[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/js/jquery/ui/ -> sortable.js (source)

   1  /*!
   2   * jQuery UI Sortable 1.14.2
   3   * https://jqueryui.com
   4   *
   5   * Copyright OpenJS Foundation and other contributors
   6   * Released under the MIT license.
   7   * https://jquery.org/license
   8   */
   9  
  10  //>>label: Sortable
  11  //>>group: Interactions
  12  //>>description: Enables items in a list to be sorted using the mouse.
  13  //>>docs: https://api.jqueryui.com/sortable/
  14  //>>demos: https://jqueryui.com/sortable/
  15  //>>css.structure: ../../themes/base/sortable.css
  16  
  17  ( function( factory ) {
  18      "use strict";
  19  
  20      if ( typeof define === "function" && define.amd ) {
  21  
  22          // AMD. Register as an anonymous module.
  23          define( [
  24              "jquery",
  25              "./mouse",
  26              "../data",
  27              "../scroll-parent",
  28              "../version",
  29              "../widget"
  30          ], factory );
  31      } else {
  32  
  33          // Browser globals
  34          factory( jQuery );
  35      }
  36  } )( function( $ ) {
  37  "use strict";
  38  
  39  return $.widget( "ui.sortable", $.ui.mouse, {
  40      version: "1.14.2",
  41      widgetEventPrefix: "sort",
  42      ready: false,
  43      options: {
  44          appendTo: "parent",
  45          axis: false,
  46          connectWith: false,
  47          containment: false,
  48          cursor: "auto",
  49          cursorAt: false,
  50          dropOnEmpty: true,
  51          forcePlaceholderSize: false,
  52          forceHelperSize: false,
  53          grid: false,
  54          handle: false,
  55          helper: "original",
  56          items: "> *",
  57          opacity: false,
  58          placeholder: false,
  59          revert: false,
  60          scroll: true,
  61          scrollSensitivity: 20,
  62          scrollSpeed: 20,
  63          scope: "default",
  64          tolerance: "intersect",
  65          zIndex: 1000,
  66  
  67          // Callbacks
  68          activate: null,
  69          beforeStop: null,
  70          change: null,
  71          deactivate: null,
  72          out: null,
  73          over: null,
  74          receive: null,
  75          remove: null,
  76          sort: null,
  77          start: null,
  78          stop: null,
  79          update: null
  80      },
  81  
  82      _isOverAxis: function( x, reference, size ) {
  83          return ( x >= reference ) && ( x < ( reference + size ) );
  84      },
  85  
  86      _isFloating: function( item ) {
  87          return ( /left|right/ ).test( item.css( "float" ) ) ||
  88              ( /inline|table-cell/ ).test( item.css( "display" ) );
  89      },
  90  
  91      _create: function() {
  92          this.containerCache = {};
  93          this._addClass( "ui-sortable" );
  94  
  95          //Get the items
  96          this.refresh();
  97  
  98          //Let's determine the parent's offset
  99          this.offset = this.element.offset();
 100  
 101          //Initialize mouse events for interaction
 102          this._mouseInit();
 103  
 104          this._setHandleClassName();
 105  
 106          //We're ready to go
 107          this.ready = true;
 108  
 109      },
 110  
 111      _setOption: function( key, value ) {
 112          this._super( key, value );
 113  
 114          if ( key === "handle" ) {
 115              this._setHandleClassName();
 116          }
 117      },
 118  
 119      _setHandleClassName: function() {
 120          var that = this;
 121          this._removeClass( this.element.find( ".ui-sortable-handle" ), "ui-sortable-handle" );
 122          $.each( this.items, function() {
 123              that._addClass(
 124                  this.instance.options.handle ?
 125                      this.item.find( this.instance.options.handle ) :
 126                      this.item,
 127                  "ui-sortable-handle"
 128              );
 129          } );
 130      },
 131  
 132      _destroy: function() {
 133          this._mouseDestroy();
 134  
 135          for ( var i = this.items.length - 1; i >= 0; i-- ) {
 136              this.items[ i ].item.removeData( this.widgetName + "-item" );
 137          }
 138  
 139          return this;
 140      },
 141  
 142      _mouseCapture: function( event, overrideHandle ) {
 143          var currentItem = null,
 144              validHandle = false,
 145              that = this;
 146  
 147          if ( this.reverting ) {
 148              return false;
 149          }
 150  
 151          if ( this.options.disabled || this.options.type === "static" ) {
 152              return false;
 153          }
 154  
 155          //We have to refresh the items data once first
 156          this._refreshItems( event );
 157  
 158          //Find out if the clicked node (or one of its parents) is a actual item in this.items
 159          $( event.target ).parents().each( function() {
 160              if ( $.data( this, that.widgetName + "-item" ) === that ) {
 161                  currentItem = $( this );
 162                  return false;
 163              }
 164          } );
 165          if ( $.data( event.target, that.widgetName + "-item" ) === that ) {
 166              currentItem = $( event.target );
 167          }
 168  
 169          if ( !currentItem ) {
 170              return false;
 171          }
 172          if ( this.options.handle && !overrideHandle ) {
 173              $( this.options.handle, currentItem ).find( "*" ).addBack().each( function() {
 174                  if ( this === event.target ) {
 175                      validHandle = true;
 176                  }
 177              } );
 178              if ( !validHandle ) {
 179                  return false;
 180              }
 181          }
 182  
 183          this.currentItem = currentItem;
 184          this._removeCurrentsFromItems();
 185          return true;
 186  
 187      },
 188  
 189      _mouseStart: function( event, overrideHandle, noActivation ) {
 190  
 191          var i, body,
 192              o = this.options;
 193  
 194          this.currentContainer = this;
 195  
 196          //We only need to call refreshPositions, because the refreshItems call has been moved to
 197          // mouseCapture
 198          this.refreshPositions();
 199  
 200          //Prepare the dragged items parent
 201          this.appendTo = $( o.appendTo !== "parent" ?
 202                  o.appendTo :
 203                  this.currentItem.parent() );
 204  
 205          //Create and append the visible helper
 206          this.helper = this._createHelper( event );
 207  
 208          //Cache the helper size
 209          this._cacheHelperProportions();
 210  
 211          /*
 212           * - Position generation -
 213           * This block generates everything position related - it's the core of draggables.
 214           */
 215  
 216          //Cache the margins of the original element
 217          this._cacheMargins();
 218  
 219          //The element's absolute position on the page minus margins
 220          this.offset = this.currentItem.offset();
 221          this.offset = {
 222              top: this.offset.top - this.margins.top,
 223              left: this.offset.left - this.margins.left
 224          };
 225  
 226          $.extend( this.offset, {
 227              click: { //Where the click happened, relative to the element
 228                  left: event.pageX - this.offset.left,
 229                  top: event.pageY - this.offset.top
 230              },
 231  
 232              // This is a relative to absolute position minus the actual position calculation -
 233              // only used for relative positioned helper
 234              relative: this._getRelativeOffset()
 235          } );
 236  
 237          // After we get the helper offset, but before we get the parent offset we can
 238          // change the helper's position to absolute
 239          // TODO: Still need to figure out a way to make relative sorting possible
 240          this.helper.css( "position", "absolute" );
 241          this.cssPosition = this.helper.css( "position" );
 242  
 243          //Adjust the mouse offset relative to the helper if "cursorAt" is supplied
 244          if ( o.cursorAt ) {
 245              this._adjustOffsetFromHelper( o.cursorAt );
 246          }
 247  
 248          //Cache the former DOM position
 249          this.domPosition = {
 250              prev: this.currentItem.prev()[ 0 ],
 251              parent: this.currentItem.parent()[ 0 ]
 252          };
 253  
 254          // If the helper is not the original, hide the original so it's not playing any role during
 255          // the drag, won't cause anything bad this way
 256          if ( this.helper[ 0 ] !== this.currentItem[ 0 ] ) {
 257              this.currentItem.hide();
 258          }
 259  
 260          //Create the placeholder
 261          this._createPlaceholder();
 262  
 263          //Get the next scrolling parent
 264          this.scrollParent = this.placeholder.scrollParent();
 265  
 266          $.extend( this.offset, {
 267              parent: this._getParentOffset()
 268          } );
 269  
 270          //Set a containment if given in the options
 271          if ( o.containment ) {
 272              this._setContainment();
 273          }
 274  
 275          if ( o.cursor && o.cursor !== "auto" ) { // cursor option
 276              body = this.document.find( "body" );
 277  
 278              this._storedStylesheet =
 279                  $( "<style>*{ cursor: " + o.cursor + " !important; }</style>" ).appendTo( body );
 280          }
 281  
 282          // We need to make sure to grab the zIndex before setting the
 283          // opacity, because setting the opacity to anything lower than 1
 284          // causes the zIndex to change from "auto" to 0.
 285          if ( o.zIndex ) { // zIndex option
 286              if ( this.helper.css( "zIndex" ) ) {
 287                  this._storedZIndex = this.helper.css( "zIndex" );
 288              }
 289              this.helper.css( "zIndex", o.zIndex );
 290          }
 291  
 292          if ( o.opacity ) { // opacity option
 293              if ( this.helper.css( "opacity" ) ) {
 294                  this._storedOpacity = this.helper.css( "opacity" );
 295              }
 296              this.helper.css( "opacity", o.opacity );
 297          }
 298  
 299          //Prepare scrolling
 300          if ( this.scrollParent[ 0 ] !== this.document[ 0 ] &&
 301                  this.scrollParent[ 0 ].tagName !== "HTML" ) {
 302              this.overflowOffset = this.scrollParent.offset();
 303          }
 304  
 305          //Call callbacks
 306          this._trigger( "start", event, this._uiHash() );
 307  
 308          //Recache the helper size
 309          if ( !this._preserveHelperProportions ) {
 310              this._cacheHelperProportions();
 311          }
 312  
 313          //Post "activate" events to possible containers
 314          if ( !noActivation ) {
 315              for ( i = this.containers.length - 1; i >= 0; i-- ) {
 316                  this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) );
 317              }
 318          }
 319  
 320          //Prepare possible droppables
 321          if ( $.ui.ddmanager ) {
 322              $.ui.ddmanager.current = this;
 323          }
 324  
 325          if ( $.ui.ddmanager && !o.dropBehaviour ) {
 326              $.ui.ddmanager.prepareOffsets( this, event );
 327          }
 328  
 329          this.dragging = true;
 330  
 331          this._addClass( this.helper, "ui-sortable-helper" );
 332  
 333          //Move the helper, if needed
 334          if ( !this.helper.parent().is( this.appendTo ) ) {
 335              this.helper.detach().appendTo( this.appendTo );
 336  
 337              //Update position
 338              this.offset.parent = this._getParentOffset();
 339          }
 340  
 341          //Generate the original position
 342          this.position = this.originalPosition = this._generatePosition( event );
 343          this.originalPageX = event.pageX;
 344          this.originalPageY = event.pageY;
 345          this.lastPositionAbs = this.positionAbs = this._convertPositionTo( "absolute" );
 346  
 347          this._mouseDrag( event );
 348  
 349          return true;
 350  
 351      },
 352  
 353      _scroll: function( event ) {
 354          var o = this.options,
 355              scrolled = false;
 356  
 357          if ( this.scrollParent[ 0 ] !== this.document[ 0 ] &&
 358                  this.scrollParent[ 0 ].tagName !== "HTML" ) {
 359  
 360              if ( ( this.overflowOffset.top + this.scrollParent[ 0 ].offsetHeight ) -
 361                      event.pageY < o.scrollSensitivity ) {
 362                  this.scrollParent[ 0 ].scrollTop =
 363                      scrolled = this.scrollParent[ 0 ].scrollTop + o.scrollSpeed;
 364              } else if ( event.pageY - this.overflowOffset.top < o.scrollSensitivity ) {
 365                  this.scrollParent[ 0 ].scrollTop =
 366                      scrolled = this.scrollParent[ 0 ].scrollTop - o.scrollSpeed;
 367              }
 368  
 369              if ( ( this.overflowOffset.left + this.scrollParent[ 0 ].offsetWidth ) -
 370                      event.pageX < o.scrollSensitivity ) {
 371                  this.scrollParent[ 0 ].scrollLeft = scrolled =
 372                      this.scrollParent[ 0 ].scrollLeft + o.scrollSpeed;
 373              } else if ( event.pageX - this.overflowOffset.left < o.scrollSensitivity ) {
 374                  this.scrollParent[ 0 ].scrollLeft = scrolled =
 375                      this.scrollParent[ 0 ].scrollLeft - o.scrollSpeed;
 376              }
 377  
 378          } else {
 379  
 380              if ( event.pageY - this.document.scrollTop() < o.scrollSensitivity ) {
 381                  scrolled = this.document.scrollTop( this.document.scrollTop() - o.scrollSpeed );
 382              } else if ( this.window.height() - ( event.pageY - this.document.scrollTop() ) <
 383                      o.scrollSensitivity ) {
 384                  scrolled = this.document.scrollTop( this.document.scrollTop() + o.scrollSpeed );
 385              }
 386  
 387              if ( event.pageX - this.document.scrollLeft() < o.scrollSensitivity ) {
 388                  scrolled = this.document.scrollLeft(
 389                      this.document.scrollLeft() - o.scrollSpeed
 390                  );
 391              } else if ( this.window.width() - ( event.pageX - this.document.scrollLeft() ) <
 392                      o.scrollSensitivity ) {
 393                  scrolled = this.document.scrollLeft(
 394                      this.document.scrollLeft() + o.scrollSpeed
 395                  );
 396              }
 397  
 398          }
 399  
 400          return scrolled;
 401      },
 402  
 403      _mouseDrag: function( event ) {
 404          var i, item, itemElement, intersection,
 405              o = this.options;
 406  
 407          //Compute the helpers position
 408          this.position = this._generatePosition( event );
 409          this.positionAbs = this._convertPositionTo( "absolute" );
 410  
 411          //Set the helper position
 412          if ( !this.options.axis || this.options.axis !== "y" ) {
 413              this.helper[ 0 ].style.left = this.position.left + "px";
 414          }
 415          if ( !this.options.axis || this.options.axis !== "x" ) {
 416              this.helper[ 0 ].style.top = this.position.top + "px";
 417          }
 418  
 419          //Do scrolling
 420          if ( o.scroll ) {
 421              if ( this._scroll( event ) !== false ) {
 422  
 423                  //Update item positions used in position checks
 424                  this._refreshItemPositions( true );
 425  
 426                  if ( $.ui.ddmanager && !o.dropBehaviour ) {
 427                      $.ui.ddmanager.prepareOffsets( this, event );
 428                  }
 429              }
 430          }
 431  
 432          this.dragDirection = {
 433              vertical: this._getDragVerticalDirection(),
 434              horizontal: this._getDragHorizontalDirection()
 435          };
 436  
 437          //Rearrange
 438          for ( i = this.items.length - 1; i >= 0; i-- ) {
 439  
 440              //Cache variables and intersection, continue if no intersection
 441              item = this.items[ i ];
 442              itemElement = item.item[ 0 ];
 443              intersection = this._intersectsWithPointer( item );
 444              if ( !intersection ) {
 445                  continue;
 446              }
 447  
 448              // Only put the placeholder inside the current Container, skip all
 449              // items from other containers. This works because when moving
 450              // an item from one container to another the
 451              // currentContainer is switched before the placeholder is moved.
 452              //
 453              // Without this, moving items in "sub-sortables" can cause
 454              // the placeholder to jitter between the outer and inner container.
 455              if ( item.instance !== this.currentContainer ) {
 456                  continue;
 457              }
 458  
 459              // Cannot intersect with itself
 460              // no useless actions that have been done before
 461              // no action if the item moved is the parent of the item checked
 462              if ( itemElement !== this.currentItem[ 0 ] &&
 463                  this.placeholder[ intersection === 1 ?
 464                  "next" : "prev" ]()[ 0 ] !== itemElement &&
 465                  !$.contains( this.placeholder[ 0 ], itemElement ) &&
 466                  ( this.options.type === "semi-dynamic" ?
 467                      !$.contains( this.element[ 0 ], itemElement ) :
 468                      true
 469                  )
 470              ) {
 471  
 472                  this.direction = intersection === 1 ? "down" : "up";
 473  
 474                  if ( this.options.tolerance === "pointer" ||
 475                          this._intersectsWithSides( item ) ) {
 476                      this._rearrange( event, item );
 477                  } else {
 478                      break;
 479                  }
 480  
 481                  this._trigger( "change", event, this._uiHash() );
 482                  break;
 483              }
 484          }
 485  
 486          //Post events to containers
 487          this._contactContainers( event );
 488  
 489          //Interconnect with droppables
 490          if ( $.ui.ddmanager ) {
 491              $.ui.ddmanager.drag( this, event );
 492          }
 493  
 494          //Call callbacks
 495          this._trigger( "sort", event, this._uiHash() );
 496  
 497          this.lastPositionAbs = this.positionAbs;
 498          return false;
 499  
 500      },
 501  
 502      _mouseStop: function( event, noPropagation ) {
 503  
 504          if ( !event ) {
 505              return;
 506          }
 507  
 508          //If we are using droppables, inform the manager about the drop
 509          if ( $.ui.ddmanager && !this.options.dropBehaviour ) {
 510              $.ui.ddmanager.drop( this, event );
 511          }
 512  
 513          if ( this.options.revert ) {
 514              var that = this,
 515                  cur = this.placeholder.offset(),
 516                  axis = this.options.axis,
 517                  animation = {};
 518  
 519              if ( !axis || axis === "x" ) {
 520                  animation.left = cur.left - this.offset.parent.left - this.margins.left +
 521                      ( this.offsetParent[ 0 ] === this.document[ 0 ].body ?
 522                          0 :
 523                          this.offsetParent[ 0 ].scrollLeft
 524                      );
 525              }
 526              if ( !axis || axis === "y" ) {
 527                  animation.top = cur.top - this.offset.parent.top - this.margins.top +
 528                      ( this.offsetParent[ 0 ] === this.document[ 0 ].body ?
 529                          0 :
 530                          this.offsetParent[ 0 ].scrollTop
 531                      );
 532              }
 533              this.reverting = true;
 534              $( this.helper ).animate(
 535                  animation,
 536                  parseInt( this.options.revert, 10 ) || 500,
 537                  function() {
 538                      that._clear( event );
 539                  }
 540              );
 541          } else {
 542              this._clear( event, noPropagation );
 543          }
 544  
 545          return false;
 546  
 547      },
 548  
 549      cancel: function() {
 550  
 551          if ( this.dragging ) {
 552  
 553              this._mouseUp( new $.Event( "mouseup", { target: null } ) );
 554  
 555              if ( this.options.helper === "original" ) {
 556                  this.currentItem.css( this._storedCSS );
 557                  this._removeClass( this.currentItem, "ui-sortable-helper" );
 558              } else {
 559                  this.currentItem.show();
 560              }
 561  
 562              //Post deactivating events to containers
 563              for ( var i = this.containers.length - 1; i >= 0; i-- ) {
 564                  this.containers[ i ]._trigger( "deactivate", null, this._uiHash( this ) );
 565                  if ( this.containers[ i ].containerCache.over ) {
 566                      this.containers[ i ]._trigger( "out", null, this._uiHash( this ) );
 567                      this.containers[ i ].containerCache.over = 0;
 568                  }
 569              }
 570  
 571          }
 572  
 573          if ( this.placeholder ) {
 574  
 575              //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately,
 576              // it unbinds ALL events from the original node!
 577              if ( this.placeholder[ 0 ].parentNode ) {
 578                  this.placeholder[ 0 ].parentNode.removeChild( this.placeholder[ 0 ] );
 579              }
 580              if ( this.options.helper !== "original" && this.helper &&
 581                      this.helper[ 0 ].parentNode ) {
 582                  this.helper.remove();
 583              }
 584  
 585              $.extend( this, {
 586                  helper: null,
 587                  dragging: false,
 588                  reverting: false,
 589                  _noFinalSort: null
 590              } );
 591  
 592              if ( this.domPosition.prev ) {
 593                  $( this.domPosition.prev ).after( this.currentItem );
 594              } else {
 595                  $( this.domPosition.parent ).prepend( this.currentItem );
 596              }
 597          }
 598  
 599          return this;
 600  
 601      },
 602  
 603      serialize: function( o ) {
 604  
 605          var items = this._getItemsAsjQuery( o && o.connected ),
 606              str = [];
 607          o = o || {};
 608  
 609          $( items ).each( function() {
 610              var res = ( $( o.item || this ).attr( o.attribute || "id" ) || "" )
 611                  .match( o.expression || ( /(.+)[\-=_](.+)/ ) );
 612              if ( res ) {
 613                  str.push(
 614                      ( o.key || res[ 1 ] + "[]" ) +
 615                      "=" + ( o.key && o.expression ? res[ 1 ] : res[ 2 ] ) );
 616              }
 617          } );
 618  
 619          if ( !str.length && o.key ) {
 620              str.push( o.key + "=" );
 621          }
 622  
 623          return str.join( "&" );
 624  
 625      },
 626  
 627      toArray: function( o ) {
 628  
 629          var items = this._getItemsAsjQuery( o && o.connected ),
 630              ret = [];
 631  
 632          o = o || {};
 633  
 634          items.each( function() {
 635              ret.push( $( o.item || this ).attr( o.attribute || "id" ) || "" );
 636          } );
 637          return ret;
 638  
 639      },
 640  
 641      /* Be careful with the following core functions */
 642      _intersectsWith: function( item ) {
 643  
 644          var x1 = this.positionAbs.left,
 645              x2 = x1 + this.helperProportions.width,
 646              y1 = this.positionAbs.top,
 647              y2 = y1 + this.helperProportions.height,
 648              l = item.left,
 649              r = l + item.width,
 650              t = item.top,
 651              b = t + item.height,
 652              dyClick = this.offset.click.top,
 653              dxClick = this.offset.click.left,
 654              isOverElementHeight = ( this.options.axis === "x" ) || ( ( y1 + dyClick ) > t &&
 655                  ( y1 + dyClick ) < b ),
 656              isOverElementWidth = ( this.options.axis === "y" ) || ( ( x1 + dxClick ) > l &&
 657                  ( x1 + dxClick ) < r ),
 658              isOverElement = isOverElementHeight && isOverElementWidth;
 659  
 660          if ( this.options.tolerance === "pointer" ||
 661              this.options.forcePointerForContainers ||
 662              ( this.options.tolerance !== "pointer" &&
 663                  this.helperProportions[ this.floating ? "width" : "height" ] >
 664                  item[ this.floating ? "width" : "height" ] )
 665          ) {
 666              return isOverElement;
 667          } else {
 668  
 669              return ( l < x1 + ( this.helperProportions.width / 2 ) && // Right Half
 670                  x2 - ( this.helperProportions.width / 2 ) < r && // Left Half
 671                  t < y1 + ( this.helperProportions.height / 2 ) && // Bottom Half
 672                  y2 - ( this.helperProportions.height / 2 ) < b ); // Top Half
 673  
 674          }
 675      },
 676  
 677      _intersectsWithPointer: function( item ) {
 678          var verticalDirection, horizontalDirection,
 679              isOverElementHeight = ( this.options.axis === "x" ) ||
 680                  this._isOverAxis(
 681                      this.positionAbs.top + this.offset.click.top, item.top, item.height ),
 682              isOverElementWidth = ( this.options.axis === "y" ) ||
 683                  this._isOverAxis(
 684                      this.positionAbs.left + this.offset.click.left, item.left, item.width ),
 685              isOverElement = isOverElementHeight && isOverElementWidth;
 686  
 687          if ( !isOverElement ) {
 688              return false;
 689          }
 690  
 691          verticalDirection = this.dragDirection.vertical;
 692          horizontalDirection = this.dragDirection.horizontal;
 693  
 694          return this.floating ?
 695              ( ( horizontalDirection === "right" || verticalDirection === "down" ) ? 2 : 1 ) :
 696              ( verticalDirection && ( verticalDirection === "down" ? 2 : 1 ) );
 697  
 698      },
 699  
 700      _intersectsWithSides: function( item ) {
 701  
 702          var isOverBottomHalf = this._isOverAxis( this.positionAbs.top +
 703                  this.offset.click.top, item.top + ( item.height / 2 ), item.height ),
 704              isOverRightHalf = this._isOverAxis( this.positionAbs.left +
 705                  this.offset.click.left, item.left + ( item.width / 2 ), item.width ),
 706              verticalDirection = this.dragDirection.vertical,
 707              horizontalDirection = this.dragDirection.horizontal;
 708  
 709          if ( this.floating && horizontalDirection ) {
 710              return ( ( horizontalDirection === "right" && isOverRightHalf ) ||
 711                  ( horizontalDirection === "left" && !isOverRightHalf ) );
 712          } else {
 713              return verticalDirection && ( ( verticalDirection === "down" && isOverBottomHalf ) ||
 714                  ( verticalDirection === "up" && !isOverBottomHalf ) );
 715          }
 716  
 717      },
 718  
 719      _getDragVerticalDirection: function() {
 720          var delta = this.positionAbs.top - this.lastPositionAbs.top;
 721          return delta !== 0 && ( delta > 0 ? "down" : "up" );
 722      },
 723  
 724      _getDragHorizontalDirection: function() {
 725          var delta = this.positionAbs.left - this.lastPositionAbs.left;
 726          return delta !== 0 && ( delta > 0 ? "right" : "left" );
 727      },
 728  
 729      refresh: function( event ) {
 730          this._refreshItems( event );
 731          this._setHandleClassName();
 732          this.refreshPositions();
 733          return this;
 734      },
 735  
 736      _connectWith: function() {
 737          var options = this.options;
 738          return options.connectWith.constructor === String ?
 739              [ options.connectWith ] :
 740              options.connectWith;
 741      },
 742  
 743      _getItemsAsjQuery: function( connected ) {
 744  
 745          var i, j, cur, inst,
 746              items = [],
 747              queries = [],
 748              connectWith = this._connectWith();
 749  
 750          if ( connectWith && connected ) {
 751              for ( i = connectWith.length - 1; i >= 0; i-- ) {
 752                  cur = $( connectWith[ i ], this.document[ 0 ] );
 753                  for ( j = cur.length - 1; j >= 0; j-- ) {
 754                      inst = $.data( cur[ j ], this.widgetFullName );
 755                      if ( inst && inst !== this && !inst.options.disabled ) {
 756                          queries.push( [ typeof inst.options.items === "function" ?
 757                              inst.options.items.call( inst.element ) :
 758                              $( inst.options.items, inst.element )
 759                                  .not( ".ui-sortable-helper" )
 760                                  .not( ".ui-sortable-placeholder" ), inst ] );
 761                      }
 762                  }
 763              }
 764          }
 765  
 766          queries.push( [ typeof this.options.items === "function" ?
 767              this.options.items
 768                  .call( this.element, null, { options: this.options, item: this.currentItem } ) :
 769              $( this.options.items, this.element )
 770                  .not( ".ui-sortable-helper" )
 771                  .not( ".ui-sortable-placeholder" ), this ] );
 772  
 773  		function addItems() {
 774              items.push( this );
 775          }
 776          for ( i = queries.length - 1; i >= 0; i-- ) {
 777              queries[ i ][ 0 ].each( addItems );
 778          }
 779  
 780          return $( items );
 781  
 782      },
 783  
 784      _removeCurrentsFromItems: function() {
 785  
 786          var list = this.currentItem.find( ":data(" + this.widgetName + "-item)" );
 787  
 788          this.items = $.grep( this.items, function( item ) {
 789              for ( var j = 0; j < list.length; j++ ) {
 790                  if ( list[ j ] === item.item[ 0 ] ) {
 791                      return false;
 792                  }
 793              }
 794              return true;
 795          } );
 796  
 797      },
 798  
 799      _refreshItems: function( event ) {
 800  
 801          this.items = [];
 802          this.containers = [ this ];
 803  
 804          var i, j, cur, inst, targetData, _queries, item, queriesLength,
 805              items = this.items,
 806              queries = [ [ typeof this.options.items === "function" ?
 807                  this.options.items.call( this.element[ 0 ], event, { item: this.currentItem } ) :
 808                  $( this.options.items, this.element ), this ] ],
 809              connectWith = this._connectWith();
 810  
 811          //Shouldn't be run the first time through due to massive slow-down
 812          if ( connectWith && this.ready ) {
 813              for ( i = connectWith.length - 1; i >= 0; i-- ) {
 814                  cur = $( connectWith[ i ], this.document[ 0 ] );
 815                  for ( j = cur.length - 1; j >= 0; j-- ) {
 816                      inst = $.data( cur[ j ], this.widgetFullName );
 817                      if ( inst && inst !== this && !inst.options.disabled ) {
 818                          queries.push( [ typeof inst.options.items === "function" ?
 819                              inst.options.items
 820                                  .call( inst.element[ 0 ], event, { item: this.currentItem } ) :
 821                              $( inst.options.items, inst.element ), inst ] );
 822                          this.containers.push( inst );
 823                      }
 824                  }
 825              }
 826          }
 827  
 828          for ( i = queries.length - 1; i >= 0; i-- ) {
 829              targetData = queries[ i ][ 1 ];
 830              _queries = queries[ i ][ 0 ];
 831  
 832              for ( j = 0, queriesLength = _queries.length; j < queriesLength; j++ ) {
 833                  item = $( _queries[ j ] );
 834  
 835                  // Data for target checking (mouse manager)
 836                  item.data( this.widgetName + "-item", targetData );
 837  
 838                  items.push( {
 839                      item: item,
 840                      instance: targetData,
 841                      width: 0, height: 0,
 842                      left: 0, top: 0
 843                  } );
 844              }
 845          }
 846  
 847      },
 848  
 849      _refreshItemPositions: function( fast ) {
 850          var i, item, t, p;
 851  
 852          for ( i = this.items.length - 1; i >= 0; i-- ) {
 853              item = this.items[ i ];
 854  
 855              //We ignore calculating positions of all connected containers when we're not over them
 856              if ( this.currentContainer && item.instance !== this.currentContainer &&
 857                      item.item[ 0 ] !== this.currentItem[ 0 ] ) {
 858                  continue;
 859              }
 860  
 861              t = this.options.toleranceElement ?
 862                  $( this.options.toleranceElement, item.item ) :
 863                  item.item;
 864  
 865              if ( !fast ) {
 866                  item.width = t.outerWidth();
 867                  item.height = t.outerHeight();
 868              }
 869  
 870              p = t.offset();
 871              item.left = p.left;
 872              item.top = p.top;
 873          }
 874      },
 875  
 876      refreshPositions: function( fast ) {
 877  
 878          // Determine whether items are being displayed horizontally
 879          this.floating = this.items.length ?
 880              this.options.axis === "x" || this._isFloating( this.items[ 0 ].item ) :
 881              false;
 882  
 883          // This has to be redone because due to the item being moved out/into the offsetParent,
 884          // the offsetParent's position will change
 885          if ( this.offsetParent && this.helper ) {
 886              this.offset.parent = this._getParentOffset();
 887          }
 888  
 889          this._refreshItemPositions( fast );
 890  
 891          var i, p;
 892  
 893          if ( this.options.custom && this.options.custom.refreshContainers ) {
 894              this.options.custom.refreshContainers.call( this );
 895          } else {
 896              for ( i = this.containers.length - 1; i >= 0; i-- ) {
 897                  p = this.containers[ i ].element.offset();
 898                  this.containers[ i ].containerCache.left = p.left;
 899                  this.containers[ i ].containerCache.top = p.top;
 900                  this.containers[ i ].containerCache.width =
 901                      this.containers[ i ].element.outerWidth();
 902                  this.containers[ i ].containerCache.height =
 903                      this.containers[ i ].element.outerHeight();
 904              }
 905          }
 906  
 907          return this;
 908      },
 909  
 910      _createPlaceholder: function( that ) {
 911          that = that || this;
 912          var className, nodeName,
 913              o = that.options;
 914  
 915          if ( !o.placeholder || o.placeholder.constructor === String ) {
 916              className = o.placeholder;
 917              nodeName = that.currentItem[ 0 ].nodeName.toLowerCase();
 918              o.placeholder = {
 919                  element: function() {
 920  
 921                      var element = $( "<" + nodeName + ">", that.document[ 0 ] );
 922  
 923                      that._addClass( element, "ui-sortable-placeholder",
 924                              className || that.currentItem[ 0 ].className )
 925                          ._removeClass( element, "ui-sortable-helper" );
 926  
 927                      if ( nodeName === "tbody" ) {
 928                          that._createTrPlaceholder(
 929                              that.currentItem.find( "tr" ).eq( 0 ),
 930                              $( "<tr>", that.document[ 0 ] ).appendTo( element )
 931                          );
 932                      } else if ( nodeName === "tr" ) {
 933                          that._createTrPlaceholder( that.currentItem, element );
 934                      } else if ( nodeName === "img" ) {
 935                          element.attr( "src", that.currentItem.attr( "src" ) );
 936                      }
 937  
 938                      if ( !className ) {
 939                          element.css( "visibility", "hidden" );
 940                      }
 941  
 942                      return element;
 943                  },
 944                  update: function( container, p ) {
 945  
 946                      // 1. If a className is set as 'placeholder option, we don't force sizes -
 947                      // the class is responsible for that
 948                      // 2. The option 'forcePlaceholderSize can be enabled to force it even if a
 949                      // class name is specified
 950                      if ( className && !o.forcePlaceholderSize ) {
 951                          return;
 952                      }
 953  
 954                      // If the element doesn't have a actual height or width by itself (without
 955                      // styles coming from a stylesheet), it receives the inline height and width
 956                      // from the dragged item. Or, if it's a tbody or tr, it's going to have a height
 957                      // anyway since we're populating them with <td>s above, but they're unlikely to
 958                      // be the correct height on their own if the row heights are dynamic, so we'll
 959                      // always assign the height of the dragged item given forcePlaceholderSize
 960                      // is true.
 961                      if ( !p.height() || ( o.forcePlaceholderSize &&
 962                              ( nodeName === "tbody" || nodeName === "tr" ) ) ) {
 963                          p.height(
 964                              that.currentItem.innerHeight() -
 965                              parseInt( that.currentItem.css( "paddingTop" ) || 0, 10 ) -
 966                              parseInt( that.currentItem.css( "paddingBottom" ) || 0, 10 ) );
 967                      }
 968                      if ( !p.width() ) {
 969                          p.width(
 970                              that.currentItem.innerWidth() -
 971                              parseInt( that.currentItem.css( "paddingLeft" ) || 0, 10 ) -
 972                              parseInt( that.currentItem.css( "paddingRight" ) || 0, 10 ) );
 973                      }
 974                  }
 975              };
 976          }
 977  
 978          //Create the placeholder
 979          that.placeholder = $( o.placeholder.element.call( that.element, that.currentItem ) );
 980  
 981          //Append it after the actual current item
 982          that.currentItem.after( that.placeholder );
 983  
 984          //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
 985          o.placeholder.update( that, that.placeholder );
 986  
 987      },
 988  
 989      _createTrPlaceholder: function( sourceTr, targetTr ) {
 990          var that = this;
 991  
 992          sourceTr.children().each( function() {
 993              $( "<td>&#160;</td>", that.document[ 0 ] )
 994                  .attr( "colspan", $( this ).attr( "colspan" ) || 1 )
 995                  .appendTo( targetTr );
 996          } );
 997      },
 998  
 999      _contactContainers: function( event ) {
1000          var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, cur, nearBottom,
1001              floating, axis,
1002              innermostContainer = null,
1003              innermostIndex = null;
1004  
1005          // Get innermost container that intersects with item
1006          for ( i = this.containers.length - 1; i >= 0; i-- ) {
1007  
1008              // Never consider a container that's located within the item itself
1009              if ( $.contains( this.currentItem[ 0 ], this.containers[ i ].element[ 0 ] ) ) {
1010                  continue;
1011              }
1012  
1013              if ( this._intersectsWith( this.containers[ i ].containerCache ) ) {
1014  
1015                  // If we've already found a container and it's more "inner" than this, then continue
1016                  if ( innermostContainer &&
1017                          $.contains(
1018                              this.containers[ i ].element[ 0 ],
1019                              innermostContainer.element[ 0 ] ) ) {
1020                      continue;
1021                  }
1022  
1023                  innermostContainer = this.containers[ i ];
1024                  innermostIndex = i;
1025  
1026              } else {
1027  
1028                  // container doesn't intersect. trigger "out" event if necessary
1029                  if ( this.containers[ i ].containerCache.over ) {
1030                      this.containers[ i ]._trigger( "out", event, this._uiHash( this ) );
1031                      this.containers[ i ].containerCache.over = 0;
1032                  }
1033              }
1034  
1035          }
1036  
1037          // If no intersecting containers found, return
1038          if ( !innermostContainer ) {
1039              return;
1040          }
1041  
1042          // Move the item into the container if it's not there already
1043          if ( this.containers.length === 1 ) {
1044              if ( !this.containers[ innermostIndex ].containerCache.over ) {
1045                  this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash( this ) );
1046                  this.containers[ innermostIndex ].containerCache.over = 1;
1047              }
1048          } else {
1049  
1050              // When entering a new container, we will find the item with the least distance and
1051              // append our item near it
1052              dist = 10000;
1053              itemWithLeastDistance = null;
1054              floating = innermostContainer.floating || this._isFloating( this.currentItem );
1055              posProperty = floating ? "left" : "top";
1056              sizeProperty = floating ? "width" : "height";
1057              axis = floating ? "pageX" : "pageY";
1058  
1059              for ( j = this.items.length - 1; j >= 0; j-- ) {
1060                  if ( !$.contains(
1061                          this.containers[ innermostIndex ].element[ 0 ], this.items[ j ].item[ 0 ] )
1062                  ) {
1063                      continue;
1064                  }
1065                  if ( this.items[ j ].item[ 0 ] === this.currentItem[ 0 ] ) {
1066                      continue;
1067                  }
1068  
1069                  cur = this.items[ j ].item.offset()[ posProperty ];
1070                  nearBottom = false;
1071                  if ( event[ axis ] - cur > this.items[ j ][ sizeProperty ] / 2 ) {
1072                      nearBottom = true;
1073                  }
1074  
1075                  if ( Math.abs( event[ axis ] - cur ) < dist ) {
1076                      dist = Math.abs( event[ axis ] - cur );
1077                      itemWithLeastDistance = this.items[ j ];
1078                      this.direction = nearBottom ? "up" : "down";
1079                  }
1080              }
1081  
1082              //Check if dropOnEmpty is enabled
1083              if ( !itemWithLeastDistance && !this.options.dropOnEmpty ) {
1084                  return;
1085              }
1086  
1087              if ( this.currentContainer === this.containers[ innermostIndex ] ) {
1088                  if ( !this.currentContainer.containerCache.over ) {
1089                      this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash() );
1090                      this.currentContainer.containerCache.over = 1;
1091                  }
1092                  return;
1093              }
1094  
1095              if ( itemWithLeastDistance ) {
1096                  this._rearrange( event, itemWithLeastDistance, null, true );
1097              } else {
1098                  this._rearrange( event, null, this.containers[ innermostIndex ].element, true );
1099              }
1100              this._trigger( "change", event, this._uiHash() );
1101              this.containers[ innermostIndex ]._trigger( "change", event, this._uiHash( this ) );
1102              this.currentContainer = this.containers[ innermostIndex ];
1103  
1104              //Update the placeholder
1105              this.options.placeholder.update( this.currentContainer, this.placeholder );
1106  
1107              //Update scrollParent
1108              this.scrollParent = this.placeholder.scrollParent();
1109  
1110              //Update overflowOffset
1111              if ( this.scrollParent[ 0 ] !== this.document[ 0 ] &&
1112                      this.scrollParent[ 0 ].tagName !== "HTML" ) {
1113                  this.overflowOffset = this.scrollParent.offset();
1114              }
1115  
1116              this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash( this ) );
1117              this.containers[ innermostIndex ].containerCache.over = 1;
1118          }
1119  
1120      },
1121  
1122      _createHelper: function( event ) {
1123  
1124          var o = this.options,
1125              helper = typeof o.helper === "function" ?
1126                  $( o.helper.apply( this.element[ 0 ], [ event, this.currentItem ] ) ) :
1127                  ( o.helper === "clone" ? this.currentItem.clone() : this.currentItem );
1128  
1129          //Add the helper to the DOM if that didn't happen already
1130          if ( !helper.parents( "body" ).length ) {
1131              this.appendTo[ 0 ].appendChild( helper[ 0 ] );
1132          }
1133  
1134          if ( helper[ 0 ] === this.currentItem[ 0 ] ) {
1135              this._storedCSS = {
1136                  width: this.currentItem[ 0 ].style.width,
1137                  height: this.currentItem[ 0 ].style.height,
1138                  position: this.currentItem.css( "position" ),
1139                  top: this.currentItem.css( "top" ),
1140                  left: this.currentItem.css( "left" )
1141              };
1142          }
1143  
1144          if ( !helper[ 0 ].style.width || o.forceHelperSize ) {
1145              helper.width( this.currentItem.width() );
1146          }
1147          if ( !helper[ 0 ].style.height || o.forceHelperSize ) {
1148              helper.height( this.currentItem.height() );
1149          }
1150  
1151          return helper;
1152  
1153      },
1154  
1155      _adjustOffsetFromHelper: function( obj ) {
1156          if ( typeof obj === "string" ) {
1157              obj = obj.split( " " );
1158          }
1159          if ( Array.isArray( obj ) ) {
1160              obj = { left: +obj[ 0 ], top: +obj[ 1 ] || 0 };
1161          }
1162          if ( "left" in obj ) {
1163              this.offset.click.left = obj.left + this.margins.left;
1164          }
1165          if ( "right" in obj ) {
1166              this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
1167          }
1168          if ( "top" in obj ) {
1169              this.offset.click.top = obj.top + this.margins.top;
1170          }
1171          if ( "bottom" in obj ) {
1172              this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
1173          }
1174      },
1175  
1176      _getParentOffset: function() {
1177  
1178          //Get the offsetParent and cache its position
1179          this.offsetParent = this.helper.offsetParent();
1180          var po = this.offsetParent.offset();
1181  
1182          // This is a special case where we need to modify a offset calculated on start, since the
1183          // following happened:
1184          // 1. The position of the helper is absolute, so it's position is calculated based on the
1185          // next positioned parent
1186          // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't
1187          // the document, which means that the scroll is included in the initial calculation of the
1188          // offset of the parent, and never recalculated upon drag
1189          if ( this.cssPosition === "absolute" && this.scrollParent[ 0 ] !== this.document[ 0 ] &&
1190                  $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) {
1191              po.left += this.scrollParent.scrollLeft();
1192              po.top += this.scrollParent.scrollTop();
1193          }
1194  
1195          // This needs to be actually done for all browsers, since pageX/pageY includes
1196          // this information.
1197          if ( this.offsetParent[ 0 ] === this.document[ 0 ].body ) {
1198              po = { top: 0, left: 0 };
1199          }
1200  
1201          return {
1202              top: po.top + ( parseInt( this.offsetParent.css( "borderTopWidth" ), 10 ) || 0 ),
1203              left: po.left + ( parseInt( this.offsetParent.css( "borderLeftWidth" ), 10 ) || 0 )
1204          };
1205  
1206      },
1207  
1208      _getRelativeOffset: function() {
1209  
1210          if ( this.cssPosition === "relative" ) {
1211              var p = this.currentItem.position();
1212              return {
1213                  top: p.top - ( parseInt( this.helper.css( "top" ), 10 ) || 0 ) +
1214                      this.scrollParent.scrollTop(),
1215                  left: p.left - ( parseInt( this.helper.css( "left" ), 10 ) || 0 ) +
1216                      this.scrollParent.scrollLeft()
1217              };
1218          } else {
1219              return { top: 0, left: 0 };
1220          }
1221  
1222      },
1223  
1224      _cacheMargins: function() {
1225          this.margins = {
1226              left: ( parseInt( this.currentItem.css( "marginLeft" ), 10 ) || 0 ),
1227              top: ( parseInt( this.currentItem.css( "marginTop" ), 10 ) || 0 )
1228          };
1229      },
1230  
1231      _cacheHelperProportions: function() {
1232          this.helperProportions = {
1233              width: this.helper.outerWidth(),
1234              height: this.helper.outerHeight()
1235          };
1236      },
1237  
1238      _setContainment: function() {
1239  
1240          var ce, co, over,
1241              o = this.options;
1242          if ( o.containment === "parent" ) {
1243              o.containment = this.helper[ 0 ].parentNode;
1244          }
1245          if ( o.containment === "document" || o.containment === "window" ) {
1246              this.containment = [
1247                  0 - this.offset.relative.left - this.offset.parent.left,
1248                  0 - this.offset.relative.top - this.offset.parent.top,
1249                  o.containment === "document" ?
1250                      this.document.width() :
1251                      this.window.width() - this.helperProportions.width - this.margins.left,
1252                  ( o.containment === "document" ?
1253                      ( this.document.height() || document.body.parentNode.scrollHeight ) :
1254                      this.window.height() || this.document[ 0 ].body.parentNode.scrollHeight
1255                  ) - this.helperProportions.height - this.margins.top
1256              ];
1257          }
1258  
1259          if ( !( /^(document|window|parent)$/ ).test( o.containment ) ) {
1260              ce = $( o.containment )[ 0 ];
1261              co = $( o.containment ).offset();
1262              over = ( $( ce ).css( "overflow" ) !== "hidden" );
1263  
1264              this.containment = [
1265                  co.left + ( parseInt( $( ce ).css( "borderLeftWidth" ), 10 ) || 0 ) +
1266                      ( parseInt( $( ce ).css( "paddingLeft" ), 10 ) || 0 ) - this.margins.left,
1267                  co.top + ( parseInt( $( ce ).css( "borderTopWidth" ), 10 ) || 0 ) +
1268                      ( parseInt( $( ce ).css( "paddingTop" ), 10 ) || 0 ) - this.margins.top,
1269                  co.left + ( over ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) -
1270                      ( parseInt( $( ce ).css( "borderLeftWidth" ), 10 ) || 0 ) -
1271                      ( parseInt( $( ce ).css( "paddingRight" ), 10 ) || 0 ) -
1272                      this.helperProportions.width - this.margins.left,
1273                  co.top + ( over ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) -
1274                      ( parseInt( $( ce ).css( "borderTopWidth" ), 10 ) || 0 ) -
1275                      ( parseInt( $( ce ).css( "paddingBottom" ), 10 ) || 0 ) -
1276                      this.helperProportions.height - this.margins.top
1277              ];
1278          }
1279  
1280      },
1281  
1282      _convertPositionTo: function( d, pos ) {
1283  
1284          if ( !pos ) {
1285              pos = this.position;
1286          }
1287          var mod = d === "absolute" ? 1 : -1,
1288              scroll = this.cssPosition === "absolute" &&
1289                  !( this.scrollParent[ 0 ] !== this.document[ 0 ] &&
1290                  $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ?
1291                      this.offsetParent :
1292                      this.scrollParent,
1293              scrollIsRootNode = ( /(html|body)/i ).test( scroll[ 0 ].tagName );
1294  
1295          return {
1296              top: (
1297  
1298                  // The absolute mouse position
1299                  pos.top    +
1300  
1301                  // Only for relative positioned nodes: Relative offset from element to offset parent
1302                  this.offset.relative.top * mod +
1303  
1304                  // The offsetParent's offset without borders (offset + border)
1305                  this.offset.parent.top * mod -
1306                  ( ( this.cssPosition === "fixed" ?
1307                      -this.scrollParent.scrollTop() :
1308                      ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod )
1309              ),
1310              left: (
1311  
1312                  // The absolute mouse position
1313                  pos.left +
1314  
1315                  // Only for relative positioned nodes: Relative offset from element to offset parent
1316                  this.offset.relative.left * mod +
1317  
1318                  // The offsetParent's offset without borders (offset + border)
1319                  this.offset.parent.left * mod    -
1320                  ( ( this.cssPosition === "fixed" ?
1321                      -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 :
1322                      scroll.scrollLeft() ) * mod )
1323              )
1324          };
1325  
1326      },
1327  
1328      _generatePosition: function( event ) {
1329  
1330          var top, left,
1331              o = this.options,
1332              pageX = event.pageX,
1333              pageY = event.pageY,
1334              scroll = this.cssPosition === "absolute" &&
1335                  !( this.scrollParent[ 0 ] !== this.document[ 0 ] &&
1336                  $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ?
1337                      this.offsetParent :
1338                      this.scrollParent,
1339                  scrollIsRootNode = ( /(html|body)/i ).test( scroll[ 0 ].tagName );
1340  
1341          // This is another very weird special case that only happens for relative elements:
1342          // 1. If the css position is relative
1343          // 2. and the scroll parent is the document or similar to the offset parent
1344          // we have to refresh the relative offset during the scroll so there are no jumps
1345          if ( this.cssPosition === "relative" && !( this.scrollParent[ 0 ] !== this.document[ 0 ] &&
1346                  this.scrollParent[ 0 ] !== this.offsetParent[ 0 ] ) ) {
1347              this.offset.relative = this._getRelativeOffset();
1348          }
1349  
1350          /*
1351           * - Position constraining -
1352           * Constrain the position to a mix of grid, containment.
1353           */
1354  
1355          if ( this.originalPosition ) { //If we are not dragging yet, we won't check for options
1356  
1357              if ( this.containment ) {
1358                  if ( event.pageX - this.offset.click.left < this.containment[ 0 ] ) {
1359                      pageX = this.containment[ 0 ] + this.offset.click.left;
1360                  }
1361                  if ( event.pageY - this.offset.click.top < this.containment[ 1 ] ) {
1362                      pageY = this.containment[ 1 ] + this.offset.click.top;
1363                  }
1364                  if ( event.pageX - this.offset.click.left > this.containment[ 2 ] ) {
1365                      pageX = this.containment[ 2 ] + this.offset.click.left;
1366                  }
1367                  if ( event.pageY - this.offset.click.top > this.containment[ 3 ] ) {
1368                      pageY = this.containment[ 3 ] + this.offset.click.top;
1369                  }
1370              }
1371  
1372              if ( o.grid ) {
1373                  top = this.originalPageY + Math.round( ( pageY - this.originalPageY ) /
1374                      o.grid[ 1 ] ) * o.grid[ 1 ];
1375                  pageY = this.containment ?
1376                      ( ( top - this.offset.click.top >= this.containment[ 1 ] &&
1377                          top - this.offset.click.top <= this.containment[ 3 ] ) ?
1378                              top :
1379                              ( ( top - this.offset.click.top >= this.containment[ 1 ] ) ?
1380                                  top - o.grid[ 1 ] : top + o.grid[ 1 ] ) ) :
1381                                  top;
1382  
1383                  left = this.originalPageX + Math.round( ( pageX - this.originalPageX ) /
1384                      o.grid[ 0 ] ) * o.grid[ 0 ];
1385                  pageX = this.containment ?
1386                      ( ( left - this.offset.click.left >= this.containment[ 0 ] &&
1387                          left - this.offset.click.left <= this.containment[ 2 ] ) ?
1388                              left :
1389                              ( ( left - this.offset.click.left >= this.containment[ 0 ] ) ?
1390                                  left - o.grid[ 0 ] : left + o.grid[ 0 ] ) ) :
1391                                  left;
1392              }
1393  
1394          }
1395  
1396          return {
1397              top: (
1398  
1399                  // The absolute mouse position
1400                  pageY -
1401  
1402                  // Click offset (relative to the element)
1403                  this.offset.click.top -
1404  
1405                  // Only for relative positioned nodes: Relative offset from element to offset parent
1406                  this.offset.relative.top -
1407  
1408                  // The offsetParent's offset without borders (offset + border)
1409                  this.offset.parent.top +
1410                  ( ( this.cssPosition === "fixed" ?
1411                      -this.scrollParent.scrollTop() :
1412                      ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) )
1413              ),
1414              left: (
1415  
1416                  // The absolute mouse position
1417                  pageX -
1418  
1419                  // Click offset (relative to the element)
1420                  this.offset.click.left -
1421  
1422                  // Only for relative positioned nodes: Relative offset from element to offset parent
1423                  this.offset.relative.left -
1424  
1425                  // The offsetParent's offset without borders (offset + border)
1426                  this.offset.parent.left +
1427                  ( ( this.cssPosition === "fixed" ?
1428                      -this.scrollParent.scrollLeft() :
1429                      scrollIsRootNode ? 0 : scroll.scrollLeft() ) )
1430              )
1431          };
1432  
1433      },
1434  
1435      _rearrange: function( event, i, a, hardRefresh ) {
1436  
1437          if ( a ) {
1438              a[ 0 ].appendChild( this.placeholder[ 0 ] );
1439          } else {
1440              i.item[ 0 ].parentNode.insertBefore( this.placeholder[ 0 ],
1441                  ( this.direction === "down" ? i.item[ 0 ] : i.item[ 0 ].nextSibling ) );
1442          }
1443  
1444          //Various things done here to improve the performance:
1445          // 1. we create a setTimeout, that calls refreshPositions
1446          // 2. on the instance, we have a counter variable, that get's higher after every append
1447          // 3. on the local scope, we copy the counter variable, and check in the timeout,
1448          // if it's still the same
1449          // 4. this lets only the last addition to the timeout stack through
1450          this.counter = this.counter ? ++this.counter : 1;
1451          var counter = this.counter;
1452  
1453          this._delay( function() {
1454              if ( counter === this.counter ) {
1455  
1456                  //Precompute after each DOM insertion, NOT on mousemove
1457                  this.refreshPositions( !hardRefresh );
1458              }
1459          } );
1460  
1461      },
1462  
1463      _clear: function( event, noPropagation ) {
1464  
1465          this.reverting = false;
1466  
1467          // We delay all events that have to be triggered to after the point where the placeholder
1468          // has been removed and everything else normalized again
1469          var i,
1470              delayedTriggers = [];
1471  
1472          // We first have to update the dom position of the actual currentItem
1473          // Note: don't do it if the current item is already removed (by a user), or it gets
1474          // reappended (see #4088)
1475          if ( !this._noFinalSort && this.currentItem.parent().length ) {
1476              this.placeholder.before( this.currentItem );
1477          }
1478          this._noFinalSort = null;
1479  
1480          if ( this.helper[ 0 ] === this.currentItem[ 0 ] ) {
1481              for ( i in this._storedCSS ) {
1482                  if ( this._storedCSS[ i ] === "auto" || this._storedCSS[ i ] === "static" ) {
1483                      this._storedCSS[ i ] = "";
1484                  }
1485              }
1486              this.currentItem.css( this._storedCSS );
1487              this._removeClass( this.currentItem, "ui-sortable-helper" );
1488          } else {
1489              this.currentItem.show();
1490          }
1491  
1492          if ( this.fromOutside && !noPropagation ) {
1493              delayedTriggers.push( function( event ) {
1494                  this._trigger( "receive", event, this._uiHash( this.fromOutside ) );
1495              } );
1496          }
1497          if ( ( this.fromOutside ||
1498                  this.domPosition.prev !==
1499                  this.currentItem.prev().not( ".ui-sortable-helper" )[ 0 ] ||
1500                  this.domPosition.parent !== this.currentItem.parent()[ 0 ] ) && !noPropagation ) {
1501  
1502              // Trigger update callback if the DOM position has changed
1503              delayedTriggers.push( function( event ) {
1504                  this._trigger( "update", event, this._uiHash() );
1505              } );
1506          }
1507  
1508          // Check if the items Container has Changed and trigger appropriate
1509          // events.
1510          if ( this !== this.currentContainer ) {
1511              if ( !noPropagation ) {
1512                  delayedTriggers.push( function( event ) {
1513                      this._trigger( "remove", event, this._uiHash() );
1514                  } );
1515                  delayedTriggers.push( ( function( c ) {
1516                      return function( event ) {
1517                          c._trigger( "receive", event, this._uiHash( this ) );
1518                      };
1519                  } ).call( this, this.currentContainer ) );
1520                  delayedTriggers.push( ( function( c ) {
1521                      return function( event ) {
1522                          c._trigger( "update", event, this._uiHash( this ) );
1523                      };
1524                  } ).call( this, this.currentContainer ) );
1525              }
1526          }
1527  
1528          //Post events to containers
1529  		function delayEvent( type, instance, container ) {
1530              return function( event ) {
1531                  container._trigger( type, event, instance._uiHash( instance ) );
1532              };
1533          }
1534          for ( i = this.containers.length - 1; i >= 0; i-- ) {
1535              if ( !noPropagation ) {
1536                  delayedTriggers.push( delayEvent( "deactivate", this, this.containers[ i ] ) );
1537              }
1538              if ( this.containers[ i ].containerCache.over ) {
1539                  delayedTriggers.push( delayEvent( "out", this, this.containers[ i ] ) );
1540                  this.containers[ i ].containerCache.over = 0;
1541              }
1542          }
1543  
1544          //Do what was originally in plugins
1545          if ( this._storedStylesheet ) {
1546              this._storedStylesheet.remove();
1547              this._storedStylesheet = null;
1548          }
1549          if ( this._storedOpacity ) {
1550              this.helper.css( "opacity", this._storedOpacity );
1551          }
1552          if ( this._storedZIndex ) {
1553              this.helper.css( "zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex );
1554          }
1555  
1556          this.dragging = false;
1557  
1558          if ( !noPropagation ) {
1559              this._trigger( "beforeStop", event, this._uiHash() );
1560          }
1561  
1562          //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately,
1563          // it unbinds ALL events from the original node!
1564          this.placeholder[ 0 ].parentNode.removeChild( this.placeholder[ 0 ] );
1565  
1566          if ( !this.cancelHelperRemoval ) {
1567              if ( this.helper[ 0 ] !== this.currentItem[ 0 ] ) {
1568                  this.helper.remove();
1569              }
1570              this.helper = null;
1571          }
1572  
1573          if ( !noPropagation ) {
1574              for ( i = 0; i < delayedTriggers.length; i++ ) {
1575  
1576                  // Trigger all delayed events
1577                  delayedTriggers[ i ].call( this, event );
1578              }
1579              this._trigger( "stop", event, this._uiHash() );
1580          }
1581  
1582          this.fromOutside = false;
1583          return !this.cancelHelperRemoval;
1584  
1585      },
1586  
1587      _trigger: function() {
1588          if ( $.Widget.prototype._trigger.apply( this, arguments ) === false ) {
1589              this.cancel();
1590          }
1591      },
1592  
1593      _uiHash: function( _inst ) {
1594          var inst = _inst || this;
1595          return {
1596              helper: inst.helper,
1597              placeholder: inst.placeholder || $( [] ),
1598              position: inst.position,
1599              originalPosition: inst.originalPosition,
1600              offset: inst.positionAbs,
1601              item: inst.currentItem,
1602              sender: _inst ? _inst.element : null
1603          };
1604      }
1605  
1606  } );
1607  
1608  } );


Generated : Wed Jul 22 08:20:19 2026 Cross-referenced by PHPXref