* jQuery UI Sortable 1.13.3
* Copyright OpenJS Foundation and other contributors
* Released under the MIT license.
* https://jquery.org/license
//>>description: Enables items in a list to be sorted using the mouse.
//>>docs: https://api.jqueryui.com/sortable/
//>>demos: https://jqueryui.com/sortable/
//>>css.structure: ../../themes/base/sortable.css
if ( typeof define === "function" && define.amd ) {
// AMD. Register as an anonymous module.
return $.widget( "ui.sortable", $.ui.mouse, {
widgetEventPrefix: "sort",
forcePlaceholderSize: false,
_isOverAxis: function( x, reference, size ) {
return ( x >= reference ) && ( x < ( reference + size ) );
_isFloating: function( item ) {
return ( /left|right/ ).test( item.css( "float" ) ) ||
( /inline|table-cell/ ).test( item.css( "display" ) );
this.containerCache = {};
this._addClass( "ui-sortable" );
//Let's determine the parent's offset
this.offset = this.element.offset();
//Initialize mouse events for interaction
this._setHandleClassName();
_setOption: function( key, value ) {
this._super( key, value );
if ( key === "handle" ) {
this._setHandleClassName();
_setHandleClassName: function() {
this._removeClass( this.element.find( ".ui-sortable-handle" ), "ui-sortable-handle" );
$.each( this.items, function() {
this.instance.options.handle ?
this.item.find( this.instance.options.handle ) :
for ( var i = this.items.length - 1; i >= 0; i-- ) {
this.items[ i ].item.removeData( this.widgetName + "-item" );
_mouseCapture: function( event, overrideHandle ) {
if ( this.options.disabled || this.options.type === "static" ) {
//We have to refresh the items data once first
this._refreshItems( event );
//Find out if the clicked node (or one of its parents) is a actual item in this.items
$( event.target ).parents().each( function() {
if ( $.data( this, that.widgetName + "-item" ) === that ) {
if ( $.data( event.target, that.widgetName + "-item" ) === that ) {
currentItem = $( event.target );
if ( this.options.handle && !overrideHandle ) {
$( this.options.handle, currentItem ).find( "*" ).addBack().each( function() {
if ( this === event.target ) {
this.currentItem = currentItem;
this._removeCurrentsFromItems();
_mouseStart: function( event, overrideHandle, noActivation ) {
this.currentContainer = this;
//We only need to call refreshPositions, because the refreshItems call has been moved to
//Prepare the dragged items parent
this.appendTo = $( o.appendTo !== "parent" ?
this.currentItem.parent() );
//Create and append the visible helper
this.helper = this._createHelper( event );
this._cacheHelperProportions();
* - Position generation -
* This block generates everything position related - it's the core of draggables.
//Cache the margins of the original element
//The element's absolute position on the page minus margins
this.offset = this.currentItem.offset();
top: this.offset.top - this.margins.top,
left: this.offset.left - this.margins.left
click: { //Where the click happened, relative to the element
left: event.pageX - this.offset.left,
top: event.pageY - this.offset.top
// This is a relative to absolute position minus the actual position calculation -
// only used for relative positioned helper
relative: this._getRelativeOffset()
// After we get the helper offset, but before we get the parent offset we can
// change the helper's position to absolute
// TODO: Still need to figure out a way to make relative sorting possible
this.helper.css( "position", "absolute" );
this.cssPosition = this.helper.css( "position" );
//Adjust the mouse offset relative to the helper if "cursorAt" is supplied
this._adjustOffsetFromHelper( o.cursorAt );
//Cache the former DOM position
prev: this.currentItem.prev()[ 0 ],
parent: this.currentItem.parent()[ 0 ]
// If the helper is not the original, hide the original so it's not playing any role during
// the drag, won't cause anything bad this way
if ( this.helper[ 0 ] !== this.currentItem[ 0 ] ) {
this._createPlaceholder();
//Get the next scrolling parent
this.scrollParent = this.placeholder.scrollParent();
parent: this._getParentOffset()
//Set a containment if given in the options
if ( o.cursor && o.cursor !== "auto" ) { // cursor option
body = this.document.find( "body" );
this.storedCursor = body.css( "cursor" );
body.css( "cursor", o.cursor );
$( "<style>*{ cursor: " + o.cursor + " !important; }</style>" ).appendTo( body );
// We need to make sure to grab the zIndex before setting the
// opacity, because setting the opacity to anything lower than 1
// causes the zIndex to change from "auto" to 0.
if ( o.zIndex ) { // zIndex option
if ( this.helper.css( "zIndex" ) ) {
this._storedZIndex = this.helper.css( "zIndex" );
this.helper.css( "zIndex", o.zIndex );
if ( o.opacity ) { // opacity option
if ( this.helper.css( "opacity" ) ) {
this._storedOpacity = this.helper.css( "opacity" );
this.helper.css( "opacity", o.opacity );
if ( this.scrollParent[ 0 ] !== this.document[ 0 ] &&
this.scrollParent[ 0 ].tagName !== "HTML" ) {
this.overflowOffset = this.scrollParent.offset();
this._trigger( "start", event, this._uiHash() );
//Recache the helper size
if ( !this._preserveHelperProportions ) {
this._cacheHelperProportions();
//Post "activate" events to possible containers
for ( i = this.containers.length - 1; i >= 0; i-- ) {
this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) );
//Prepare possible droppables
$.ui.ddmanager.current = this;
if ( $.ui.ddmanager && !o.dropBehaviour ) {
$.ui.ddmanager.prepareOffsets( this, event );
this._addClass( this.helper, "ui-sortable-helper" );
//Move the helper, if needed
if ( !this.helper.parent().is( this.appendTo ) ) {
this.helper.detach().appendTo( this.appendTo );
this.offset.parent = this._getParentOffset();
//Generate the original position
this.position = this.originalPosition = this._generatePosition( event );
this.originalPageX = event.pageX;
this.originalPageY = event.pageY;
this.lastPositionAbs = this.positionAbs = this._convertPositionTo( "absolute" );
this._mouseDrag( event );
_scroll: function( event ) {
if ( this.scrollParent[ 0 ] !== this.document[ 0 ] &&
this.scrollParent[ 0 ].tagName !== "HTML" ) {
if ( ( this.overflowOffset.top + this.scrollParent[ 0 ].offsetHeight ) -
event.pageY < o.scrollSensitivity ) {
this.scrollParent[ 0 ].scrollTop =
scrolled = this.scrollParent[ 0 ].scrollTop + o.scrollSpeed;
} else if ( event.pageY - this.overflowOffset.top < o.scrollSensitivity ) {
this.scrollParent[ 0 ].scrollTop =
scrolled = this.scrollParent[ 0 ].scrollTop - o.scrollSpeed;
if ( ( this.overflowOffset.left + this.scrollParent[ 0 ].offsetWidth ) -
event.pageX < o.scrollSensitivity ) {
this.scrollParent[ 0 ].scrollLeft = scrolled =
this.scrollParent[ 0 ].scrollLeft + o.scrollSpeed;
} else if ( event.pageX - this.overflowOffset.left < o.scrollSensitivity ) {
this.scrollParent[ 0 ].scrollLeft = scrolled =
this.scrollParent[ 0 ].scrollLeft - o.scrollSpeed;
if ( event.pageY - this.document.scrollTop() < o.scrollSensitivity ) {
scrolled = this.document.scrollTop( this.document.scrollTop() - o.scrollSpeed );
} else if ( this.window.height() - ( event.pageY - this.document.scrollTop() ) <
scrolled = this.document.scrollTop( this.document.scrollTop() + o.scrollSpeed );
if ( event.pageX - this.document.scrollLeft() < o.scrollSensitivity ) {
scrolled = this.document.scrollLeft(
this.document.scrollLeft() - o.scrollSpeed
} else if ( this.window.width() - ( event.pageX - this.document.scrollLeft() ) <
scrolled = this.document.scrollLeft(
this.document.scrollLeft() + o.scrollSpeed
_mouseDrag: function( event ) {
var i, item, itemElement, intersection,
//Compute the helpers position
this.position = this._generatePosition( event );
this.positionAbs = this._convertPositionTo( "absolute" );
//Set the helper position
if ( !this.options.axis || this.options.axis !== "y" ) {
this.helper[ 0 ].style.left = this.position.left + "px";
if ( !this.options.axis || this.options.axis !== "x" ) {
this.helper[ 0 ].style.top = this.position.top + "px";
if ( this._scroll( event ) !== false ) {
//Update item positions used in position checks
this._refreshItemPositions( true );
if ( $.ui.ddmanager && !o.dropBehaviour ) {
$.ui.ddmanager.prepareOffsets( this, event );
vertical: this._getDragVerticalDirection(),
horizontal: this._getDragHorizontalDirection()
for ( i = this.items.length - 1; i >= 0; i-- ) {
//Cache variables and intersection, continue if no intersection
itemElement = item.item[ 0 ];
intersection = this._intersectsWithPointer( item );
// Only put the placeholder inside the current Container, skip all
// items from other containers. This works because when moving
// an item from one container to another the
// currentContainer is switched before the placeholder is moved.
// Without this, moving items in "sub-sortables" can cause
// the placeholder to jitter between the outer and inner container.
if ( item.instance !== this.currentContainer ) {
// Cannot intersect with itself
// no useless actions that have been done before
// no action if the item moved is the parent of the item checked
if ( itemElement !== this.currentItem[ 0 ] &&
this.placeholder[ intersection === 1 ?
"next" : "prev" ]()[ 0 ] !== itemElement &&
!$.contains( this.placeholder[ 0 ], itemElement ) &&
( this.options.type === "semi-dynamic" ?
!$.contains( this.element[ 0 ], itemElement ) :
this.direction = intersection === 1 ? "down" : "up";
if ( this.options.tolerance === "pointer" ||
this._intersectsWithSides( item ) ) {
this._rearrange( event, item );
this._trigger( "change", event, this._uiHash() );
//Post events to containers
this._contactContainers( event );
//Interconnect with droppables
$.ui.ddmanager.drag( this, event );
this._trigger( "sort", event, this._uiHash() );