2 * jQuery UI Draggable 1.8.18
4 * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
5 * Dual licensed under the MIT or GPL Version 2 licenses.
6 * http://jquery.org/license
8 * http://docs.jquery.com/UI/Draggables
15 (function( $, undefined ) {
17 $.widget("ui.draggable", $.ui.mouse, {
18 widgetEventPrefix: "drag",
23 connectToSortable: false,
32 refreshPositions: false,
37 scrollSensitivity: 20,
47 if (this.options.helper == 'original' && !(/^(?:r|a|f)/).test(this.element.css("position")))
48 this.element[0].style.position = 'relative';
50 (this.options.addClasses && this.element.addClass("ui-draggable"));
51 (this.options.disabled && this.element.addClass("ui-draggable-disabled"));
58 if(!this.element.data('draggable')) return;
60 .removeData("draggable")
62 .removeClass("ui-draggable"
63 + " ui-draggable-dragging"
64 + " ui-draggable-disabled");
70 _mouseCapture: function(event) {
74 // among others, prevent a drag on a resizable-handle
75 if (this.helper || o.disabled || $(event.target).is('.ui-resizable-handle'))
78 //Quit if we're not on a valid handle
79 this.handle = this._getHandle(event);
84 $(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() {
85 $('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>')
87 width: this.offsetWidth+"px", height: this.offsetHeight+"px",
88 position: "absolute", opacity: "0.001", zIndex: 1000
90 .css($(this).offset())
99 _mouseStart: function(event) {
101 var o = this.options;
103 //Create and append the visible helper
104 this.helper = this._createHelper(event);
106 //Cache the helper size
107 this._cacheHelperProportions();
109 //If ddmanager is used for droppables, set the global draggable
111 $.ui.ddmanager.current = this;
114 * - Position generation -
115 * This block generates everything position related - it's the core of draggables.
118 //Cache the margins of the original element
119 this._cacheMargins();
121 //Store the helper's css position
122 this.cssPosition = this.helper.css("position");
123 this.scrollParent = this.helper.scrollParent();
125 //The element's absolute position on the page minus margins
126 this.offset = this.positionAbs = this.element.offset();
128 top: this.offset.top - this.margins.top,
129 left: this.offset.left - this.margins.left
132 $.extend(this.offset, {
133 click: { //Where the click happened, relative to the element
134 left: event.pageX - this.offset.left,
135 top: event.pageY - this.offset.top
137 parent: this._getParentOffset(),
138 relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
141 //Generate the original position
142 this.originalPosition = this.position = this._generatePosition(event);
143 this.originalPageX = event.pageX;
144 this.originalPageY = event.pageY;
146 //Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
147 (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
149 //Set a containment if given in the options
151 this._setContainment();
153 //Trigger event + callbacks
154 if(this._trigger("start", event) === false) {
159 //Recache the helper size
160 this._cacheHelperProportions();
162 //Prepare the droppable offsets
163 if ($.ui.ddmanager && !o.dropBehaviour)
164 $.ui.ddmanager.prepareOffsets(this, event);
166 this.helper.addClass("ui-draggable-dragging");
167 this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position
169 //If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
170 if ( $.ui.ddmanager ) $.ui.ddmanager.dragStart(this, event);
175 _mouseDrag: function(event, noPropagation) {
177 //Compute the helpers position
178 this.position = this._generatePosition(event);
179 this.positionAbs = this._convertPositionTo("absolute");
181 //Call plugins and callbacks and use the resulting position if something is returned
182 if (!noPropagation) {
183 var ui = this._uiHash();
184 if(this._trigger('drag', event, ui) === false) {
188 this.position = ui.position;
191 if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
192 if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
193 if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
198 _mouseStop: function(event) {
200 //If we are using droppables, inform the manager about the drop
202 if ($.ui.ddmanager && !this.options.dropBehaviour)
203 dropped = $.ui.ddmanager.drop(this, event);
205 //if a drop comes from outside (a sortable)
207 dropped = this.dropped;
208 this.dropped = false;
211 //if the original element is removed, don't bother to continue if helper is set to "original"
212 if((!this.element[0] || !this.element[0].parentNode) && this.options.helper == "original")
215 if((this.options.revert == "invalid" && !dropped) || (this.options.revert == "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) {
217 $(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() {
218 if(self._trigger("stop", event) !== false) {
223 if(this._trigger("stop", event) !== false) {
231 _mouseUp: function(event) {
232 if (this.options.iframeFix === true) {
233 $("div.ui-draggable-iframeFix").each(function() {
234 this.parentNode.removeChild(this);
235 }); //Remove frame helpers
238 //If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003)
239 if( $.ui.ddmanager ) $.ui.ddmanager.dragStop(this, event);
241 return $.ui.mouse.prototype._mouseUp.call(this, event);
246 if(this.helper.is(".ui-draggable-dragging")) {
256 _getHandle: function(event) {
258 var handle = !this.options.handle || !$(this.options.handle, this.element).length ? true : false;
259 $(this.options.handle, this.element)
263 if(this == event.target) handle = true;
270 _createHelper: function(event) {
272 var o = this.options;
273 var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper == 'clone' ? this.element.clone().removeAttr('id') : this.element);
275 if(!helper.parents('body').length)
276 helper.appendTo((o.appendTo == 'parent' ? this.element[0].parentNode : o.appendTo));
278 if(helper[0] != this.element[0] && !(/(fixed|absolute)/).test(helper.css("position")))
279 helper.css("position", "absolute");
285 _adjustOffsetFromHelper: function(obj) {
286 if (typeof obj == 'string') {
287 obj = obj.split(' ');
289 if ($.isArray(obj)) {
290 obj = {left: +obj[0], top: +obj[1] || 0};
293 this.offset.click.left = obj.left + this.margins.left;
295 if ('right' in obj) {
296 this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
299 this.offset.click.top = obj.top + this.margins.top;
301 if ('bottom' in obj) {
302 this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
306 _getParentOffset: function() {
308 //Get the offsetParent and cache its position
309 this.offsetParent = this.helper.offsetParent();
310 var po = this.offsetParent.offset();
312 // This is a special case where we need to modify a offset calculated on start, since the following happened:
313 // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
314 // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
315 // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
316 if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) {
317 po.left += this.scrollParent.scrollLeft();
318 po.top += this.scrollParent.scrollTop();
321 if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information
322 || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.browser.msie)) //Ugly IE fix
323 po = { top: 0, left: 0 };
326 top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
327 left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
332 _getRelativeOffset: function() {
334 if(this.cssPosition == "relative") {
335 var p = this.element.position();
337 top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
338 left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
341 return { top: 0, left: 0 };
346 _cacheMargins: function() {
348 left: (parseInt(this.element.css("marginLeft"),10) || 0),
349 top: (parseInt(this.element.css("marginTop"),10) || 0),
350 right: (parseInt(this.element.css("marginRight"),10) || 0),
351 bottom: (parseInt(this.element.css("marginBottom"),10) || 0)
355 _cacheHelperProportions: function() {
356 this.helperProportions = {
357 width: this.helper.outerWidth(),
358 height: this.helper.outerHeight()
362 _setContainment: function() {
364 var o = this.options;
365 if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
366 if(o.containment == 'document' || o.containment == 'window') this.containment = [
367 o.containment == 'document' ? 0 : $(window).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
368 o.containment == 'document' ? 0 : $(window).scrollTop() - this.offset.relative.top - this.offset.parent.top,
369 (o.containment == 'document' ? 0 : $(window).scrollLeft()) + $(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left,
370 (o.containment == 'document' ? 0 : $(window).scrollTop()) + ($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
373 if(!(/^(document|window|parent)$/).test(o.containment) && o.containment.constructor != Array) {
374 var c = $(o.containment);
375 var ce = c[0]; if(!ce) return;
377 var over = ($(ce).css("overflow") != 'hidden');
380 (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0),
381 (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0),
382 (over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left - this.margins.right,
383 (over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top - this.margins.bottom
385 this.relative_container = c;
387 } else if(o.containment.constructor == Array) {
388 this.containment = o.containment;
393 _convertPositionTo: function(d, pos) {
395 if(!pos) pos = this.position;
396 var mod = d == "absolute" ? 1 : -1;
397 var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
401 pos.top // The absolute mouse position
402 + this.offset.relative.top * mod // Only for relative positioned nodes: Relative offset from element to offset parent
403 + this.offset.parent.top * mod // The offsetParent's offset without borders (offset + border)
404 - ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
407 pos.left // The absolute mouse position
408 + this.offset.relative.left * mod // Only for relative positioned nodes: Relative offset from element to offset parent
409 + this.offset.parent.left * mod // The offsetParent's offset without borders (offset + border)
410 - ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
416 _generatePosition: function(event) {
418 var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
419 var pageX = event.pageX;
420 var pageY = event.pageY;
423 * - Position constraining -
424 * Constrain the position to a mix of grid, containment.
427 if(this.originalPosition) { //If we are not dragging yet, we won't check for options
429 if(this.containment) {
430 if (this.relative_container){
431 var co = this.relative_container.offset();
432 containment = [ this.containment[0] + co.left,
433 this.containment[1] + co.top,
434 this.containment[2] + co.left,
435 this.containment[3] + co.top ];
438 containment = this.containment;
441 if(event.pageX - this.offset.click.left < containment[0]) pageX = containment[0] + this.offset.click.left;
442 if(event.pageY - this.offset.click.top < containment[1]) pageY = containment[1] + this.offset.click.top;
443 if(event.pageX - this.offset.click.left > containment[2]) pageX = containment[2] + this.offset.click.left;
444 if(event.pageY - this.offset.click.top > containment[3]) pageY = containment[3] + this.offset.click.top;
448 //Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950)
449 var top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY;
450 pageY = containment ? (!(top - this.offset.click.top < containment[1] || top - this.offset.click.top > containment[3]) ? top : (!(top - this.offset.click.top < containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
452 var left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX;
453 pageX = containment ? (!(left - this.offset.click.left < containment[0] || left - this.offset.click.left > containment[2]) ? left : (!(left - this.offset.click.left < containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
460 pageY // The absolute mouse position
461 - this.offset.click.top // Click offset (relative to the element)
462 - this.offset.relative.top // Only for relative positioned nodes: Relative offset from element to offset parent
463 - this.offset.parent.top // The offsetParent's offset without borders (offset + border)
464 + ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
467 pageX // The absolute mouse position
468 - this.offset.click.left // Click offset (relative to the element)
469 - this.offset.relative.left // Only for relative positioned nodes: Relative offset from element to offset parent
470 - this.offset.parent.left // The offsetParent's offset without borders (offset + border)
471 + ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
478 this.helper.removeClass("ui-draggable-dragging");
479 if(this.helper[0] != this.element[0] && !this.cancelHelperRemoval) this.helper.remove();
480 //if($.ui.ddmanager) $.ui.ddmanager.current = null;
482 this.cancelHelperRemoval = false;
485 // From now on bulk stuff - mainly helpers
487 _trigger: function(type, event, ui) {
488 ui = ui || this._uiHash();
489 $.ui.plugin.call(this, type, [event, ui]);
490 if(type == "drag") this.positionAbs = this._convertPositionTo("absolute"); //The absolute position has to be recalculated after plugins
491 return $.Widget.prototype._trigger.call(this, type, event, ui);
496 _uiHash: function(event) {
499 position: this.position,
500 originalPosition: this.originalPosition,
501 offset: this.positionAbs
507 $.extend($.ui.draggable, {
511 $.ui.plugin.add("draggable", "connectToSortable", {
512 start: function(event, ui) {
514 var inst = $(this).data("draggable"), o = inst.options,
515 uiSortable = $.extend({}, ui, { item: inst.element });
517 $(o.connectToSortable).each(function() {
518 var sortable = $.data(this, 'sortable');
519 if (sortable && !sortable.options.disabled) {
520 inst.sortables.push({
522 shouldRevert: sortable.options.revert
524 sortable.refreshPositions(); // Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page).
525 sortable._trigger("activate", event, uiSortable);
530 stop: function(event, ui) {
532 //If we are still over the sortable, we fake the stop event of the sortable, but also remove helper
533 var inst = $(this).data("draggable"),
534 uiSortable = $.extend({}, ui, { item: inst.element });
536 $.each(inst.sortables, function() {
537 if(this.instance.isOver) {
539 this.instance.isOver = 0;
541 inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance
542 this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work)
544 //The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: 'valid/invalid'
545 if(this.shouldRevert) this.instance.options.revert = true;
547 //Trigger the stop of the sortable
548 this.instance._mouseStop(event);
550 this.instance.options.helper = this.instance.options._helper;
552 //If the helper has been the original item, restore properties in the sortable
553 if(inst.options.helper == 'original')
554 this.instance.currentItem.css({ top: 'auto', left: 'auto' });
557 this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance
558 this.instance._trigger("deactivate", event, uiSortable);
564 drag: function(event, ui) {
566 var inst = $(this).data("draggable"), self = this;
568 var checkPos = function(o) {
569 var dyClick = this.offset.click.top, dxClick = this.offset.click.left;
570 var helperTop = this.positionAbs.top, helperLeft = this.positionAbs.left;
571 var itemHeight = o.height, itemWidth = o.width;
572 var itemTop = o.top, itemLeft = o.left;
574 return $.ui.isOver(helperTop + dyClick, helperLeft + dxClick, itemTop, itemLeft, itemHeight, itemWidth);
577 $.each(inst.sortables, function(i) {
579 //Copy over some variables to allow calling the sortable's native _intersectsWith
580 this.instance.positionAbs = inst.positionAbs;
581 this.instance.helperProportions = inst.helperProportions;
582 this.instance.offset.click = inst.offset.click;
584 if(this.instance._intersectsWith(this.instance.containerCache)) {
586 //If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once
587 if(!this.instance.isOver) {
589 this.instance.isOver = 1;
590 //Now we fake the start of dragging for the sortable instance,
591 //by cloning the list group item, appending it to the sortable and using it as inst.currentItem
592 //We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one)
593 this.instance.currentItem = $(self).clone().removeAttr('id').appendTo(this.instance.element).data("sortable-item", true);
594 this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it
595 this.instance.options.helper = function() { return ui.helper[0]; };
597 event.target = this.instance.currentItem[0];
598 this.instance._mouseCapture(event, true);
599 this.instance._mouseStart(event, true, true);
601 //Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes
602 this.instance.offset.click.top = inst.offset.click.top;
603 this.instance.offset.click.left = inst.offset.click.left;
604 this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left;
605 this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top;
607 inst._trigger("toSortable", event);
608 inst.dropped = this.instance.element; //draggable revert needs that
609 //hack so receive/update callbacks work (mostly)
610 inst.currentItem = inst.element;
611 this.instance.fromOutside = inst;
615 //Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable
616 if(this.instance.currentItem) this.instance._mouseDrag(event);
620 //If it doesn't intersect with the sortable, and it intersected before,
621 //we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval
622 if(this.instance.isOver) {
624 this.instance.isOver = 0;
625 this.instance.cancelHelperRemoval = true;
627 //Prevent reverting on this forced stop
628 this.instance.options.revert = false;
630 // The out event needs to be triggered independently
631 this.instance._trigger('out', event, this.instance._uiHash(this.instance));
633 this.instance._mouseStop(event, true);
634 this.instance.options.helper = this.instance.options._helper;
636 //Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size
637 this.instance.currentItem.remove();
638 if(this.instance.placeholder) this.instance.placeholder.remove();
640 inst._trigger("fromSortable", event);
641 inst.dropped = false; //draggable revert needs that
651 $.ui.plugin.add("draggable", "cursor", {
652 start: function(event, ui) {
653 var t = $('body'), o = $(this).data('draggable').options;
654 if (t.css("cursor")) o._cursor = t.css("cursor");
655 t.css("cursor", o.cursor);
657 stop: function(event, ui) {
658 var o = $(this).data('draggable').options;
659 if (o._cursor) $('body').css("cursor", o._cursor);
663 $.ui.plugin.add("draggable", "opacity", {
664 start: function(event, ui) {
665 var t = $(ui.helper), o = $(this).data('draggable').options;
666 if(t.css("opacity")) o._opacity = t.css("opacity");
667 t.css('opacity', o.opacity);
669 stop: function(event, ui) {
670 var o = $(this).data('draggable').options;
671 if(o._opacity) $(ui.helper).css('opacity', o._opacity);
675 $.ui.plugin.add("draggable", "scroll", {
676 start: function(event, ui) {
677 var i = $(this).data("draggable");
678 if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') i.overflowOffset = i.scrollParent.offset();
680 drag: function(event, ui) {
682 var i = $(this).data("draggable"), o = i.options, scrolled = false;
684 if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') {
686 if(!o.axis || o.axis != 'x') {
687 if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
688 i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed;
689 else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity)
690 i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed;
693 if(!o.axis || o.axis != 'y') {
694 if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
695 i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed;
696 else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity)
697 i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed;
702 if(!o.axis || o.axis != 'x') {
703 if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
704 scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
705 else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
706 scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
709 if(!o.axis || o.axis != 'y') {
710 if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
711 scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
712 else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
713 scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
718 if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
719 $.ui.ddmanager.prepareOffsets(i, event);
724 $.ui.plugin.add("draggable", "snap", {
725 start: function(event, ui) {
727 var i = $(this).data("draggable"), o = i.options;
730 $(o.snap.constructor != String ? ( o.snap.items || ':data(draggable)' ) : o.snap).each(function() {
731 var $t = $(this); var $o = $t.offset();
732 if(this != i.element[0]) i.snapElements.push({
734 width: $t.outerWidth(), height: $t.outerHeight(),
735 top: $o.top, left: $o.left
740 drag: function(event, ui) {
742 var inst = $(this).data("draggable"), o = inst.options;
743 var d = o.snapTolerance;
745 var x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
746 y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;
748 for (var i = inst.snapElements.length - 1; i >= 0; i--){
750 var l = inst.snapElements[i].left, r = l + inst.snapElements[i].width,
751 t = inst.snapElements[i].top, b = t + inst.snapElements[i].height;
753 //Yes, I know, this is insane ;)
754 if(!((l-d < x1 && x1 < r+d && t-d < y1 && y1 < b+d) || (l-d < x1 && x1 < r+d && t-d < y2 && y2 < b+d) || (l-d < x2 && x2 < r+d && t-d < y1 && y1 < b+d) || (l-d < x2 && x2 < r+d && t-d < y2 && y2 < b+d))) {
755 if(inst.snapElements[i].snapping) (inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
756 inst.snapElements[i].snapping = false;
760 if(o.snapMode != 'inner') {
761 var ts = Math.abs(t - y2) <= d;
762 var bs = Math.abs(b - y1) <= d;
763 var ls = Math.abs(l - x2) <= d;
764 var rs = Math.abs(r - x1) <= d;
765 if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
766 if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top;
767 if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left;
768 if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left;
771 var first = (ts || bs || ls || rs);
773 if(o.snapMode != 'outer') {
774 var ts = Math.abs(t - y1) <= d;
775 var bs = Math.abs(b - y2) <= d;
776 var ls = Math.abs(l - x1) <= d;
777 var rs = Math.abs(r - x2) <= d;
778 if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top;
779 if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
780 if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left;
781 if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left;
784 if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first))
785 (inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
786 inst.snapElements[i].snapping = (ts || bs || ls || rs || first);
793 $.ui.plugin.add("draggable", "stack", {
794 start: function(event, ui) {
796 var o = $(this).data("draggable").options;
798 var group = $.makeArray($(o.stack)).sort(function(a,b) {
799 return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0);
801 if (!group.length) { return; }
803 var min = parseInt(group[0].style.zIndex) || 0;
804 $(group).each(function(i) {
805 this.style.zIndex = min + i;
808 this[0].style.zIndex = min + group.length;
813 $.ui.plugin.add("draggable", "zIndex", {
814 start: function(event, ui) {
815 var t = $(ui.helper), o = $(this).data("draggable").options;
816 if(t.css("zIndex")) o._zIndex = t.css("zIndex");
817 t.css('zIndex', o.zIndex);
819 stop: function(event, ui) {
820 var o = $(this).data("draggable").options;
821 if(o._zIndex) $(ui.helper).css('zIndex', o._zIndex);