diff --git a/addons/web/static/lib/jquery.ui/jquery-ui.css b/addons/web/static/lib/jquery.ui/jquery-ui.css index 92ad3d9d231..e1fcdc9ddcf 100644 --- a/addons/web/static/lib/jquery.ui/jquery-ui.css +++ b/addons/web/static/lib/jquery.ui/jquery-ui.css @@ -1,6 +1,6 @@ -/*! jQuery UI - v1.12.1 - 2023-09-14 +/*! jQuery UI - v1.12.1 - 2023-10-04 * http://jqueryui.com -* Includes: draggable.css, sortable.css, core.css, autocomplete.css, menu.css, theme.css +* Includes: draggable.css, core.css, autocomplete.css, menu.css, theme.css * To view and modify this theme, visit http://jqueryui.com/themeroller/?scope=&folderName=base&cornerRadiusShadow=8px&offsetLeftShadow=0px&offsetTopShadow=0px&thicknessShadow=5px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=666666&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=aaaaaa&iconColorError=cc0000&fcError=5f3f3f&borderColorError=f1a899&bgTextureError=flat&bgColorError=fddfdf&iconColorHighlight=777620&fcHighlight=777620&borderColorHighlight=dad55e&bgTextureHighlight=flat&bgColorHighlight=fffa90&iconColorActive=ffffff&fcActive=ffffff&borderColorActive=003eff&bgTextureActive=flat&bgColorActive=007fff&iconColorHover=555555&fcHover=2b2b2b&borderColorHover=cccccc&bgTextureHover=flat&bgColorHover=ededed&iconColorDefault=777777&fcDefault=454545&borderColorDefault=c5c5c5&bgTextureDefault=flat&bgColorDefault=f6f6f6&iconColorContent=444444&fcContent=333333&borderColorContent=dddddd&bgTextureContent=flat&bgColorContent=ffffff&iconColorHeader=444444&fcHeader=333333&borderColorHeader=dddddd&bgTextureHeader=flat&bgColorHeader=e9e9e9&cornerRadius=3px&fwDefault=normal&fsDefault=1em&ffDefault=Arial%2CHelvetica%2Csans-serif * Copyright jQuery Foundation and other contributors; Licensed MIT */ @@ -8,10 +8,6 @@ -ms-touch-action: none; touch-action: none; } -.ui-sortable-handle { - -ms-touch-action: none; - touch-action: none; -} /* Layout helpers ----------------------------------*/ .ui-helper-hidden { diff --git a/addons/web/static/lib/jquery.ui/jquery-ui.js b/addons/web/static/lib/jquery.ui/jquery-ui.js index b2b392d4ed0..8c8a3586093 100644 --- a/addons/web/static/lib/jquery.ui/jquery-ui.js +++ b/addons/web/static/lib/jquery.ui/jquery-ui.js @@ -1,6 +1,6 @@ -/*! jQuery UI - v1.12.1 - 2023-09-14 +/*! jQuery UI - v1.12.1 - 2023-10-04 * http://jqueryui.com -* Includes: widget.js, position.js, data.js, keycode.js, scroll-parent.js, unique-id.js, widgets/draggable.js, widgets/droppable.js, widgets/sortable.js, widgets/autocomplete.js, widgets/menu.js, widgets/mouse.js +* Includes: widget.js, position.js, data.js, keycode.js, scroll-parent.js, unique-id.js, widgets/draggable.js, widgets/droppable.js, widgets/autocomplete.js, widgets/menu.js, widgets/mouse.js * Copyright jQuery Foundation and other contributors; Licensed MIT */ (function( factory ) { @@ -3371,1542 +3371,6 @@ if ( $.uiBackCompat !== false ) { var widgetsDroppable = $.ui.droppable; -/*! - * jQuery UI Sortable 1.12.1 - * http://jqueryui.com - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - */ - -//>>label: Sortable -//>>group: Interactions -//>>description: Enables items in a list to be sorted using the mouse. -//>>docs: http://api.jqueryui.com/sortable/ -//>>demos: http://jqueryui.com/sortable/ -//>>css.structure: ../../themes/base/sortable.css - - - -var widgetsSortable = $.widget( "ui.sortable", $.ui.mouse, { - version: "1.12.1", - widgetEventPrefix: "sort", - ready: false, - options: { - appendTo: "parent", - axis: false, - connectWith: false, - containment: false, - cursor: "auto", - cursorAt: false, - dropOnEmpty: true, - forcePlaceholderSize: false, - forceHelperSize: false, - grid: false, - handle: false, - helper: "original", - items: "> *", - opacity: false, - placeholder: false, - revert: false, - scroll: true, - scrollSensitivity: 20, - scrollSpeed: 20, - scope: "default", - tolerance: "intersect", - zIndex: 1000, - - // Callbacks - activate: null, - beforeStop: null, - change: null, - deactivate: null, - out: null, - over: null, - receive: null, - remove: null, - sort: null, - start: null, - stop: null, - update: null - }, - - _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" ) ); - }, - - _create: function() { - this.containerCache = {}; - this._addClass( "ui-sortable" ); - - //Get the items - this.refresh(); - - //Let's determine the parent's offset - this.offset = this.element.offset(); - - //Initialize mouse events for interaction - this._mouseInit(); - - this._setHandleClassName(); - - //We're ready to go - this.ready = true; - - }, - - _setOption: function( key, value ) { - this._super( key, value ); - - if ( key === "handle" ) { - this._setHandleClassName(); - } - }, - - _setHandleClassName: function() { - var that = this; - this._removeClass( this.element.find( ".ui-sortable-handle" ), "ui-sortable-handle" ); - $.each( this.items, function() { - that._addClass( - this.instance.options.handle ? - this.item.find( this.instance.options.handle ) : - this.item, - "ui-sortable-handle" - ); - } ); - }, - - _destroy: function() { - this._mouseDestroy(); - - for ( var i = this.items.length - 1; i >= 0; i-- ) { - this.items[ i ].item.removeData( this.widgetName + "-item" ); - } - - return this; - }, - - _mouseCapture: function( event, overrideHandle ) { - var currentItem = null, - validHandle = false, - that = this; - - if ( this.reverting ) { - return false; - } - - if ( this.options.disabled || this.options.type === "static" ) { - return false; - } - - //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 ) { - currentItem = $( this ); - return false; - } - } ); - if ( $.data( event.target, that.widgetName + "-item" ) === that ) { - currentItem = $( event.target ); - } - - if ( !currentItem ) { - return false; - } - if ( this.options.handle && !overrideHandle ) { - $( this.options.handle, currentItem ).find( "*" ).addBack().each( function() { - if ( this === event.target ) { - validHandle = true; - } - } ); - if ( !validHandle ) { - return false; - } - } - - this.currentItem = currentItem; - this._removeCurrentsFromItems(); - return true; - - }, - - _mouseStart: function( event, overrideHandle, noActivation ) { - - var i, body, - o = this.options; - - this.currentContainer = this; - - //We only need to call refreshPositions, because the refreshItems call has been moved to - // mouseCapture - this.refreshPositions(); - - //Create and append the visible helper - this.helper = this._createHelper( event ); - - //Cache the helper size - this._cacheHelperProportions(); - - /* - * - Position generation - - * This block generates everything position related - it's the core of draggables. - */ - - //Cache the margins of the original element - this._cacheMargins(); - - //Get the next scrolling parent - this.scrollParent = this.helper.scrollParent(); - - //The element's absolute position on the page minus margins - this.offset = this.currentItem.offset(); - this.offset = { - top: this.offset.top - this.margins.top, - left: this.offset.left - this.margins.left - }; - - $.extend( this.offset, { - click: { //Where the click happened, relative to the element - left: event.pageX - this.offset.left, - top: event.pageY - this.offset.top - }, - parent: this._getParentOffset(), - - // This is a relative to absolute position minus the actual position calculation - - // only used for relative positioned helper - relative: this._getRelativeOffset() - } ); - - // Only after we got the 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" ); - - //Generate the original position - this.originalPosition = this._generatePosition( event ); - this.originalPageX = event.pageX; - this.originalPageY = event.pageY; - - //Adjust the mouse offset relative to the helper if "cursorAt" is supplied - ( o.cursorAt && this._adjustOffsetFromHelper( o.cursorAt ) ); - - //Cache the former DOM position - this.domPosition = { - 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.currentItem.hide(); - } - - //Create the placeholder - this._createPlaceholder(); - - //Set a containment if given in the options - if ( o.containment ) { - this._setContainment(); - } - - if ( o.cursor && o.cursor !== "auto" ) { // cursor option - body = this.document.find( "body" ); - - // Support: IE - this.storedCursor = body.css( "cursor" ); - body.css( "cursor", o.cursor ); - - this.storedStylesheet = - $( "" ).appendTo( body ); - } - - if ( o.opacity ) { // opacity option - if ( this.helper.css( "opacity" ) ) { - this._storedOpacity = this.helper.css( "opacity" ); - } - this.helper.css( "opacity", o.opacity ); - } - - if ( o.zIndex ) { // zIndex option - if ( this.helper.css( "zIndex" ) ) { - this._storedZIndex = this.helper.css( "zIndex" ); - } - this.helper.css( "zIndex", o.zIndex ); - } - - //Prepare scrolling - if ( this.scrollParent[ 0 ] !== this.document[ 0 ] && - this.scrollParent[ 0 ].tagName !== "HTML" ) { - this.overflowOffset = this.scrollParent.offset(); - } - - //Call callbacks - this._trigger( "start", event, this._uiHash() ); - - //Recache the helper size - if ( !this._preserveHelperProportions ) { - this._cacheHelperProportions(); - } - - //Post "activate" events to possible containers - if ( !noActivation ) { - for ( i = this.containers.length - 1; i >= 0; i-- ) { - this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) ); - } - } - - //Prepare possible droppables - if ( $.ui.ddmanager ) { - $.ui.ddmanager.current = this; - } - - if ( $.ui.ddmanager && !o.dropBehaviour ) { - $.ui.ddmanager.prepareOffsets( this, event ); - } - - this.dragging = true; - - this._addClass( this.helper, "ui-sortable-helper" ); - - // Execute the drag once - this causes the helper not to be visiblebefore getting its - // correct position - this._mouseDrag( event ); - return true; - - }, - - _mouseDrag: function( event ) { - var i, item, itemElement, intersection, - o = this.options, - scrolled = false; - - //Compute the helpers position - this.position = this._generatePosition( event ); - this.positionAbs = this._convertPositionTo( "absolute" ); - - if ( !this.lastPositionAbs ) { - this.lastPositionAbs = this.positionAbs; - } - - //Do scrolling - if ( this.options.scroll ) { - 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; - } - - } else { - - 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() ) < - o.scrollSensitivity ) { - 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() ) < - o.scrollSensitivity ) { - scrolled = this.document.scrollLeft( - this.document.scrollLeft() + o.scrollSpeed - ); - } - - } - - if ( scrolled !== false && $.ui.ddmanager && !o.dropBehaviour ) { - $.ui.ddmanager.prepareOffsets( this, event ); - } - } - - //Regenerate the absolute position used for position checks - 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"; - } - - //Rearrange - for ( i = this.items.length - 1; i >= 0; i-- ) { - - //Cache variables and intersection, continue if no intersection - item = this.items[ i ]; - itemElement = item.item[ 0 ]; - intersection = this._intersectsWithPointer( item ); - if ( !intersection ) { - continue; - } - - // 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 ) { - continue; - } - - // 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 ) : - true - ) - ) { - - this.direction = intersection === 1 ? "down" : "up"; - - if ( this.options.tolerance === "pointer" || this._intersectsWithSides( item ) ) { - this._rearrange( event, item ); - } else { - break; - } - - this._trigger( "change", event, this._uiHash() ); - break; - } - } - - //Post events to containers - this._contactContainers( event ); - - //Interconnect with droppables - if ( $.ui.ddmanager ) { - $.ui.ddmanager.drag( this, event ); - } - - //Call callbacks - this._trigger( "sort", event, this._uiHash() ); - - this.lastPositionAbs = this.positionAbs; - return false; - - }, - - _mouseStop: function( event, noPropagation ) { - - if ( !event ) { - return; - } - - //If we are using droppables, inform the manager about the drop - if ( $.ui.ddmanager && !this.options.dropBehaviour ) { - $.ui.ddmanager.drop( this, event ); - } - - if ( this.options.revert ) { - var that = this, - cur = this.placeholder.offset(), - axis = this.options.axis, - animation = {}; - - if ( !axis || axis === "x" ) { - animation.left = cur.left - this.offset.parent.left - this.margins.left + - ( this.offsetParent[ 0 ] === this.document[ 0 ].body ? - 0 : - this.offsetParent[ 0 ].scrollLeft - ); - } - if ( !axis || axis === "y" ) { - animation.top = cur.top - this.offset.parent.top - this.margins.top + - ( this.offsetParent[ 0 ] === this.document[ 0 ].body ? - 0 : - this.offsetParent[ 0 ].scrollTop - ); - } - this.reverting = true; - $( this.helper ).animate( - animation, - parseInt( this.options.revert, 10 ) || 500, - function() { - that._clear( event ); - } - ); - } else { - this._clear( event, noPropagation ); - } - - return false; - - }, - - cancel: function() { - - if ( this.dragging ) { - - this._mouseUp( new $.Event( "mouseup", { target: null } ) ); - - if ( this.options.helper === "original" ) { - this.currentItem.css( this._storedCSS ); - this._removeClass( this.currentItem, "ui-sortable-helper" ); - } else { - this.currentItem.show(); - } - - //Post deactivating events to containers - for ( var i = this.containers.length - 1; i >= 0; i-- ) { - this.containers[ i ]._trigger( "deactivate", null, this._uiHash( this ) ); - if ( this.containers[ i ].containerCache.over ) { - this.containers[ i ]._trigger( "out", null, this._uiHash( this ) ); - this.containers[ i ].containerCache.over = 0; - } - } - - } - - if ( this.placeholder ) { - - //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, - // it unbinds ALL events from the original node! - if ( this.placeholder[ 0 ].parentNode ) { - this.placeholder[ 0 ].parentNode.removeChild( this.placeholder[ 0 ] ); - } - if ( this.options.helper !== "original" && this.helper && - this.helper[ 0 ].parentNode ) { - this.helper.remove(); - } - - $.extend( this, { - helper: null, - dragging: false, - reverting: false, - _noFinalSort: null - } ); - - if ( this.domPosition.prev ) { - $( this.domPosition.prev ).after( this.currentItem ); - } else { - $( this.domPosition.parent ).prepend( this.currentItem ); - } - } - - return this; - - }, - - serialize: function( o ) { - - var items = this._getItemsAsjQuery( o && o.connected ), - str = []; - o = o || {}; - - $( items ).each( function() { - var res = ( $( o.item || this ).attr( o.attribute || "id" ) || "" ) - .match( o.expression || ( /(.+)[\-=_](.+)/ ) ); - if ( res ) { - str.push( - ( o.key || res[ 1 ] + "[]" ) + - "=" + ( o.key && o.expression ? res[ 1 ] : res[ 2 ] ) ); - } - } ); - - if ( !str.length && o.key ) { - str.push( o.key + "=" ); - } - - return str.join( "&" ); - - }, - - toArray: function( o ) { - - var items = this._getItemsAsjQuery( o && o.connected ), - ret = []; - - o = o || {}; - - items.each( function() { - ret.push( $( o.item || this ).attr( o.attribute || "id" ) || "" ); - } ); - return ret; - - }, - - /* Be careful with the following core functions */ - _intersectsWith: function( item ) { - - var x1 = this.positionAbs.left, - x2 = x1 + this.helperProportions.width, - y1 = this.positionAbs.top, - y2 = y1 + this.helperProportions.height, - l = item.left, - r = l + item.width, - t = item.top, - b = t + item.height, - dyClick = this.offset.click.top, - dxClick = this.offset.click.left, - isOverElementHeight = ( this.options.axis === "x" ) || ( ( y1 + dyClick ) > t && - ( y1 + dyClick ) < b ), - isOverElementWidth = ( this.options.axis === "y" ) || ( ( x1 + dxClick ) > l && - ( x1 + dxClick ) < r ), - isOverElement = isOverElementHeight && isOverElementWidth; - - if ( this.options.tolerance === "pointer" || - this.options.forcePointerForContainers || - ( this.options.tolerance !== "pointer" && - this.helperProportions[ this.floating ? "width" : "height" ] > - item[ this.floating ? "width" : "height" ] ) - ) { - return isOverElement; - } else { - - return ( l < x1 + ( this.helperProportions.width / 2 ) && // Right Half - x2 - ( this.helperProportions.width / 2 ) < r && // Left Half - t < y1 + ( this.helperProportions.height / 2 ) && // Bottom Half - y2 - ( this.helperProportions.height / 2 ) < b ); // Top Half - - } - }, - - _intersectsWithPointer: function( item ) { - var verticalDirection, horizontalDirection, - isOverElementHeight = ( this.options.axis === "x" ) || - this._isOverAxis( - this.positionAbs.top + this.offset.click.top, item.top, item.height ), - isOverElementWidth = ( this.options.axis === "y" ) || - this._isOverAxis( - this.positionAbs.left + this.offset.click.left, item.left, item.width ), - isOverElement = isOverElementHeight && isOverElementWidth; - - if ( !isOverElement ) { - return false; - } - - verticalDirection = this._getDragVerticalDirection(); - horizontalDirection = this._getDragHorizontalDirection(); - - return this.floating ? - ( ( horizontalDirection === "right" || verticalDirection === "down" ) ? 2 : 1 ) - : ( verticalDirection && ( verticalDirection === "down" ? 2 : 1 ) ); - - }, - - _intersectsWithSides: function( item ) { - - var isOverBottomHalf = this._isOverAxis( this.positionAbs.top + - this.offset.click.top, item.top + ( item.height / 2 ), item.height ), - isOverRightHalf = this._isOverAxis( this.positionAbs.left + - this.offset.click.left, item.left + ( item.width / 2 ), item.width ), - verticalDirection = this._getDragVerticalDirection(), - horizontalDirection = this._getDragHorizontalDirection(); - - if ( this.floating && horizontalDirection ) { - return ( ( horizontalDirection === "right" && isOverRightHalf ) || - ( horizontalDirection === "left" && !isOverRightHalf ) ); - } else { - return verticalDirection && ( ( verticalDirection === "down" && isOverBottomHalf ) || - ( verticalDirection === "up" && !isOverBottomHalf ) ); - } - - }, - - _getDragVerticalDirection: function() { - var delta = this.positionAbs.top - this.lastPositionAbs.top; - return delta !== 0 && ( delta > 0 ? "down" : "up" ); - }, - - _getDragHorizontalDirection: function() { - var delta = this.positionAbs.left - this.lastPositionAbs.left; - return delta !== 0 && ( delta > 0 ? "right" : "left" ); - }, - - refresh: function( event ) { - this._refreshItems( event ); - this._setHandleClassName(); - this.refreshPositions(); - return this; - }, - - _connectWith: function() { - var options = this.options; - return options.connectWith.constructor === String ? - [ options.connectWith ] : - options.connectWith; - }, - - _getItemsAsjQuery: function( connected ) { - - var i, j, cur, inst, - items = [], - queries = [], - connectWith = this._connectWith(); - - if ( connectWith && connected ) { - for ( i = connectWith.length - 1; i >= 0; i-- ) { - cur = $( connectWith[ i ], this.document[ 0 ] ); - for ( j = cur.length - 1; j >= 0; j-- ) { - inst = $.data( cur[ j ], this.widgetFullName ); - if ( inst && inst !== this && !inst.options.disabled ) { - queries.push( [ $.isFunction( inst.options.items ) ? - inst.options.items.call( inst.element ) : - $( inst.options.items, inst.element ) - .not( ".ui-sortable-helper" ) - .not( ".ui-sortable-placeholder" ), inst ] ); - } - } - } - } - - queries.push( [ $.isFunction( this.options.items ) ? - this.options.items - .call( this.element, null, { options: this.options, item: this.currentItem } ) : - $( this.options.items, this.element ) - .not( ".ui-sortable-helper" ) - .not( ".ui-sortable-placeholder" ), this ] ); - - function addItems() { - items.push( this ); - } - for ( i = queries.length - 1; i >= 0; i-- ) { - queries[ i ][ 0 ].each( addItems ); - } - - return $( items ); - - }, - - _removeCurrentsFromItems: function() { - - var list = this.currentItem.find( ":data(" + this.widgetName + "-item)" ); - - this.items = $.grep( this.items, function( item ) { - for ( var j = 0; j < list.length; j++ ) { - if ( list[ j ] === item.item[ 0 ] ) { - return false; - } - } - return true; - } ); - - }, - - _refreshItems: function( event ) { - - this.items = []; - this.containers = [ this ]; - - var i, j, cur, inst, targetData, _queries, item, queriesLength, - items = this.items, - queries = [ [ $.isFunction( this.options.items ) ? - this.options.items.call( this.element[ 0 ], event, { item: this.currentItem } ) : - $( this.options.items, this.element ), this ] ], - connectWith = this._connectWith(); - - //Shouldn't be run the first time through due to massive slow-down - if ( connectWith && this.ready ) { - for ( i = connectWith.length - 1; i >= 0; i-- ) { - cur = $( connectWith[ i ], this.document[ 0 ] ); - for ( j = cur.length - 1; j >= 0; j-- ) { - inst = $.data( cur[ j ], this.widgetFullName ); - if ( inst && inst !== this && !inst.options.disabled ) { - queries.push( [ $.isFunction( inst.options.items ) ? - inst.options.items - .call( inst.element[ 0 ], event, { item: this.currentItem } ) : - $( inst.options.items, inst.element ), inst ] ); - this.containers.push( inst ); - } - } - } - } - - for ( i = queries.length - 1; i >= 0; i-- ) { - targetData = queries[ i ][ 1 ]; - _queries = queries[ i ][ 0 ]; - - for ( j = 0, queriesLength = _queries.length; j < queriesLength; j++ ) { - item = $( _queries[ j ] ); - - // Data for target checking (mouse manager) - item.data( this.widgetName + "-item", targetData ); - - items.push( { - item: item, - instance: targetData, - width: 0, height: 0, - left: 0, top: 0 - } ); - } - } - - }, - - refreshPositions: function( fast ) { - - // Determine whether items are being displayed horizontally - this.floating = this.items.length ? - this.options.axis === "x" || this._isFloating( this.items[ 0 ].item ) : - false; - - //This has to be redone because due to the item being moved out/into the offsetParent, - // the offsetParent's position will change - if ( this.offsetParent && this.helper ) { - this.offset.parent = this._getParentOffset(); - } - - var i, item, t, p; - - for ( i = this.items.length - 1; i >= 0; i-- ) { - item = this.items[ i ]; - - //We ignore calculating positions of all connected containers when we're not over them - if ( item.instance !== this.currentContainer && this.currentContainer && - item.item[ 0 ] !== this.currentItem[ 0 ] ) { - continue; - } - - t = this.options.toleranceElement ? - $( this.options.toleranceElement, item.item ) : - item.item; - - if ( !fast ) { - item.width = t.outerWidth(); - item.height = t.outerHeight(); - } - - p = t.offset(); - item.left = p.left; - item.top = p.top; - } - - if ( this.options.custom && this.options.custom.refreshContainers ) { - this.options.custom.refreshContainers.call( this ); - } else { - for ( i = this.containers.length - 1; i >= 0; i-- ) { - p = this.containers[ i ].element.offset(); - this.containers[ i ].containerCache.left = p.left; - this.containers[ i ].containerCache.top = p.top; - this.containers[ i ].containerCache.width = - this.containers[ i ].element.outerWidth(); - this.containers[ i ].containerCache.height = - this.containers[ i ].element.outerHeight(); - } - } - - return this; - }, - - _createPlaceholder: function( that ) { - that = that || this; - var className, - o = that.options; - - if ( !o.placeholder || o.placeholder.constructor === String ) { - className = o.placeholder; - o.placeholder = { - element: function() { - - var nodeName = that.currentItem[ 0 ].nodeName.toLowerCase(), - element = $( "<" + nodeName + ">", that.document[ 0 ] ); - - that._addClass( element, "ui-sortable-placeholder", - className || that.currentItem[ 0 ].className ) - ._removeClass( element, "ui-sortable-helper" ); - - if ( nodeName === "tbody" ) { - that._createTrPlaceholder( - that.currentItem.find( "tr" ).eq( 0 ), - $( "", that.document[ 0 ] ).appendTo( element ) - ); - } else if ( nodeName === "tr" ) { - that._createTrPlaceholder( that.currentItem, element ); - } else if ( nodeName === "img" ) { - element.attr( "src", that.currentItem.attr( "src" ) ); - } - - if ( !className ) { - element.css( "visibility", "hidden" ); - } - - return element; - }, - update: function( container, p ) { - - // 1. If a className is set as 'placeholder option, we don't force sizes - - // the class is responsible for that - // 2. The option 'forcePlaceholderSize can be enabled to force it even if a - // class name is specified - if ( className && !o.forcePlaceholderSize ) { - return; - } - - //If the element doesn't have a actual height by itself (without styles coming - // from a stylesheet), it receives the inline height from the dragged item - if ( !p.height() ) { - p.height( - that.currentItem.innerHeight() - - parseInt( that.currentItem.css( "paddingTop" ) || 0, 10 ) - - parseInt( that.currentItem.css( "paddingBottom" ) || 0, 10 ) ); - } - if ( !p.width() ) { - p.width( - that.currentItem.innerWidth() - - parseInt( that.currentItem.css( "paddingLeft" ) || 0, 10 ) - - parseInt( that.currentItem.css( "paddingRight" ) || 0, 10 ) ); - } - } - }; - } - - //Create the placeholder - that.placeholder = $( o.placeholder.element.call( that.element, that.currentItem ) ); - - //Append it after the actual current item - that.currentItem.after( that.placeholder ); - - //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317) - o.placeholder.update( that, that.placeholder ); - - }, - - _createTrPlaceholder: function( sourceTr, targetTr ) { - var that = this; - - sourceTr.children().each( function() { - $( " ", that.document[ 0 ] ) - .attr( "colspan", $( this ).attr( "colspan" ) || 1 ) - .appendTo( targetTr ); - } ); - }, - - _contactContainers: function( event ) { - var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, cur, nearBottom, - floating, axis, - innermostContainer = null, - innermostIndex = null; - - // Get innermost container that intersects with item - for ( i = this.containers.length - 1; i >= 0; i-- ) { - - // Never consider a container that's located within the item itself - if ( $.contains( this.currentItem[ 0 ], this.containers[ i ].element[ 0 ] ) ) { - continue; - } - - if ( this._intersectsWith( this.containers[ i ].containerCache ) ) { - - // If we've already found a container and it's more "inner" than this, then continue - if ( innermostContainer && - $.contains( - this.containers[ i ].element[ 0 ], - innermostContainer.element[ 0 ] ) ) { - continue; - } - - innermostContainer = this.containers[ i ]; - innermostIndex = i; - - } else { - - // container doesn't intersect. trigger "out" event if necessary - if ( this.containers[ i ].containerCache.over ) { - this.containers[ i ]._trigger( "out", event, this._uiHash( this ) ); - this.containers[ i ].containerCache.over = 0; - } - } - - } - - // If no intersecting containers found, return - if ( !innermostContainer ) { - return; - } - - // Move the item into the container if it's not there already - if ( this.containers.length === 1 ) { - if ( !this.containers[ innermostIndex ].containerCache.over ) { - this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash( this ) ); - this.containers[ innermostIndex ].containerCache.over = 1; - } - } else { - - // When entering a new container, we will find the item with the least distance and - // append our item near it - dist = 10000; - itemWithLeastDistance = null; - floating = innermostContainer.floating || this._isFloating( this.currentItem ); - posProperty = floating ? "left" : "top"; - sizeProperty = floating ? "width" : "height"; - axis = floating ? "pageX" : "pageY"; - - for ( j = this.items.length - 1; j >= 0; j-- ) { - if ( !$.contains( - this.containers[ innermostIndex ].element[ 0 ], this.items[ j ].item[ 0 ] ) - ) { - continue; - } - if ( this.items[ j ].item[ 0 ] === this.currentItem[ 0 ] ) { - continue; - } - - cur = this.items[ j ].item.offset()[ posProperty ]; - nearBottom = false; - if ( event[ axis ] - cur > this.items[ j ][ sizeProperty ] / 2 ) { - nearBottom = true; - } - - if ( Math.abs( event[ axis ] - cur ) < dist ) { - dist = Math.abs( event[ axis ] - cur ); - itemWithLeastDistance = this.items[ j ]; - this.direction = nearBottom ? "up" : "down"; - } - } - - //Check if dropOnEmpty is enabled - if ( !itemWithLeastDistance && !this.options.dropOnEmpty ) { - return; - } - - if ( this.currentContainer === this.containers[ innermostIndex ] ) { - if ( !this.currentContainer.containerCache.over ) { - this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash() ); - this.currentContainer.containerCache.over = 1; - } - return; - } - - itemWithLeastDistance ? - this._rearrange( event, itemWithLeastDistance, null, true ) : - this._rearrange( event, null, this.containers[ innermostIndex ].element, true ); - this._trigger( "change", event, this._uiHash() ); - this.containers[ innermostIndex ]._trigger( "change", event, this._uiHash( this ) ); - this.currentContainer = this.containers[ innermostIndex ]; - - //Update the placeholder - this.options.placeholder.update( this.currentContainer, this.placeholder ); - - this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash( this ) ); - this.containers[ innermostIndex ].containerCache.over = 1; - } - - }, - - _createHelper: function( event ) { - - var o = this.options, - helper = $.isFunction( o.helper ) ? - $( o.helper.apply( this.element[ 0 ], [ event, this.currentItem ] ) ) : - ( o.helper === "clone" ? this.currentItem.clone() : this.currentItem ); - - //Add the helper to the DOM if that didn't happen already - if ( !helper.parents( "body" ).length ) { - $( o.appendTo !== "parent" ? - o.appendTo : - this.currentItem[ 0 ].parentNode )[ 0 ].appendChild( helper[ 0 ] ); - } - - if ( helper[ 0 ] === this.currentItem[ 0 ] ) { - this._storedCSS = { - width: this.currentItem[ 0 ].style.width, - height: this.currentItem[ 0 ].style.height, - position: this.currentItem.css( "position" ), - top: this.currentItem.css( "top" ), - left: this.currentItem.css( "left" ) - }; - } - - if ( !helper[ 0 ].style.width || o.forceHelperSize ) { - helper.width( this.currentItem.width() ); - } - if ( !helper[ 0 ].style.height || o.forceHelperSize ) { - helper.height( this.currentItem.height() ); - } - - return helper; - - }, - - _adjustOffsetFromHelper: function( obj ) { - if ( typeof obj === "string" ) { - obj = obj.split( " " ); - } - if ( $.isArray( obj ) ) { - obj = { left: +obj[ 0 ], top: +obj[ 1 ] || 0 }; - } - if ( "left" in obj ) { - this.offset.click.left = obj.left + this.margins.left; - } - if ( "right" in obj ) { - this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left; - } - if ( "top" in obj ) { - this.offset.click.top = obj.top + this.margins.top; - } - if ( "bottom" in obj ) { - this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top; - } - }, - - _getParentOffset: function() { - - //Get the offsetParent and cache its position - this.offsetParent = this.helper.offsetParent(); - var po = this.offsetParent.offset(); - - // This is a special case where we need to modify a offset calculated on start, since the - // following happened: - // 1. The position of the helper is absolute, so it's position is calculated based on the - // next positioned parent - // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't - // the document, which means that the scroll is included in the initial calculation of the - // offset of the parent, and never recalculated upon drag - if ( this.cssPosition === "absolute" && this.scrollParent[ 0 ] !== this.document[ 0 ] && - $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) { - po.left += this.scrollParent.scrollLeft(); - po.top += this.scrollParent.scrollTop(); - } - - // This needs to be actually done for all browsers, since pageX/pageY includes this - // information with an ugly IE fix - if ( this.offsetParent[ 0 ] === this.document[ 0 ].body || - ( this.offsetParent[ 0 ].tagName && - this.offsetParent[ 0 ].tagName.toLowerCase() === "html" && $.ui.ie ) ) { - po = { top: 0, left: 0 }; - } - - return { - top: po.top + ( parseInt( this.offsetParent.css( "borderTopWidth" ), 10 ) || 0 ), - left: po.left + ( parseInt( this.offsetParent.css( "borderLeftWidth" ), 10 ) || 0 ) - }; - - }, - - _getRelativeOffset: function() { - - if ( this.cssPosition === "relative" ) { - var p = this.currentItem.position(); - return { - top: p.top - ( parseInt( this.helper.css( "top" ), 10 ) || 0 ) + - this.scrollParent.scrollTop(), - left: p.left - ( parseInt( this.helper.css( "left" ), 10 ) || 0 ) + - this.scrollParent.scrollLeft() - }; - } else { - return { top: 0, left: 0 }; - } - - }, - - _cacheMargins: function() { - this.margins = { - left: ( parseInt( this.currentItem.css( "marginLeft" ), 10 ) || 0 ), - top: ( parseInt( this.currentItem.css( "marginTop" ), 10 ) || 0 ) - }; - }, - - _cacheHelperProportions: function() { - this.helperProportions = { - width: this.helper.outerWidth(), - height: this.helper.outerHeight() - }; - }, - - _setContainment: function() { - - var ce, co, over, - o = this.options; - if ( o.containment === "parent" ) { - o.containment = this.helper[ 0 ].parentNode; - } - if ( o.containment === "document" || o.containment === "window" ) { - this.containment = [ - 0 - this.offset.relative.left - this.offset.parent.left, - 0 - this.offset.relative.top - this.offset.parent.top, - o.containment === "document" ? - this.document.width() : - this.window.width() - this.helperProportions.width - this.margins.left, - ( o.containment === "document" ? - ( this.document.height() || document.body.parentNode.scrollHeight ) : - this.window.height() || this.document[ 0 ].body.parentNode.scrollHeight - ) - this.helperProportions.height - this.margins.top - ]; - } - - if ( !( /^(document|window|parent)$/ ).test( o.containment ) ) { - ce = $( o.containment )[ 0 ]; - co = $( o.containment ).offset(); - over = ( $( ce ).css( "overflow" ) !== "hidden" ); - - this.containment = [ - co.left + ( parseInt( $( ce ).css( "borderLeftWidth" ), 10 ) || 0 ) + - ( parseInt( $( ce ).css( "paddingLeft" ), 10 ) || 0 ) - this.margins.left, - co.top + ( parseInt( $( ce ).css( "borderTopWidth" ), 10 ) || 0 ) + - ( parseInt( $( ce ).css( "paddingTop" ), 10 ) || 0 ) - this.margins.top, - co.left + ( 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, - co.top + ( 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 - ]; - } - - }, - - _convertPositionTo: function( d, pos ) { - - if ( !pos ) { - pos = this.position; - } - var mod = d === "absolute" ? 1 : -1, - scroll = this.cssPosition === "absolute" && - !( this.scrollParent[ 0 ] !== this.document[ 0 ] && - $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? - this.offsetParent : - this.scrollParent, - scrollIsRootNode = ( /(html|body)/i ).test( scroll[ 0 ].tagName ); - - return { - top: ( - - // The absolute mouse position - pos.top + - - // Only for relative positioned nodes: Relative offset from element to offset parent - this.offset.relative.top * mod + - - // The offsetParent's offset without borders (offset + border) - this.offset.parent.top * mod - - ( ( this.cssPosition === "fixed" ? - -this.scrollParent.scrollTop() : - ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod ) - ), - left: ( - - // The absolute mouse position - pos.left + - - // Only for relative positioned nodes: Relative offset from element to offset parent - this.offset.relative.left * mod + - - // The offsetParent's offset without borders (offset + border) - this.offset.parent.left * mod - - ( ( this.cssPosition === "fixed" ? - -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : - scroll.scrollLeft() ) * mod ) - ) - }; - - }, - - _generatePosition: function( event ) { - - var top, left, - o = this.options, - pageX = event.pageX, - pageY = event.pageY, - scroll = this.cssPosition === "absolute" && - !( this.scrollParent[ 0 ] !== this.document[ 0 ] && - $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? - this.offsetParent : - this.scrollParent, - scrollIsRootNode = ( /(html|body)/i ).test( scroll[ 0 ].tagName ); - - // This is another very weird special case that only happens for relative elements: - // 1. If the css position is relative - // 2. and the scroll parent is the document or similar to the offset parent - // we have to refresh the relative offset during the scroll so there are no jumps - if ( this.cssPosition === "relative" && !( this.scrollParent[ 0 ] !== this.document[ 0 ] && - this.scrollParent[ 0 ] !== this.offsetParent[ 0 ] ) ) { - this.offset.relative = this._getRelativeOffset(); - } - - /* - * - Position constraining - - * Constrain the position to a mix of grid, containment. - */ - - if ( this.originalPosition ) { //If we are not dragging yet, we won't check for options - - if ( this.containment ) { - if ( event.pageX - this.offset.click.left < this.containment[ 0 ] ) { - pageX = this.containment[ 0 ] + this.offset.click.left; - } - if ( event.pageY - this.offset.click.top < this.containment[ 1 ] ) { - pageY = this.containment[ 1 ] + this.offset.click.top; - } - if ( event.pageX - this.offset.click.left > this.containment[ 2 ] ) { - pageX = this.containment[ 2 ] + this.offset.click.left; - } - if ( event.pageY - this.offset.click.top > this.containment[ 3 ] ) { - pageY = this.containment[ 3 ] + this.offset.click.top; - } - } - - if ( o.grid ) { - top = this.originalPageY + Math.round( ( pageY - this.originalPageY ) / - o.grid[ 1 ] ) * o.grid[ 1 ]; - pageY = this.containment ? - ( ( top - this.offset.click.top >= this.containment[ 1 ] && - top - this.offset.click.top <= this.containment[ 3 ] ) ? - top : - ( ( top - this.offset.click.top >= this.containment[ 1 ] ) ? - top - o.grid[ 1 ] : top + o.grid[ 1 ] ) ) : - top; - - left = this.originalPageX + Math.round( ( pageX - this.originalPageX ) / - o.grid[ 0 ] ) * o.grid[ 0 ]; - pageX = this.containment ? - ( ( left - this.offset.click.left >= this.containment[ 0 ] && - left - this.offset.click.left <= this.containment[ 2 ] ) ? - left : - ( ( left - this.offset.click.left >= this.containment[ 0 ] ) ? - left - o.grid[ 0 ] : left + o.grid[ 0 ] ) ) : - left; - } - - } - - return { - top: ( - - // The absolute mouse position - pageY - - - // Click offset (relative to the element) - this.offset.click.top - - - // Only for relative positioned nodes: Relative offset from element to offset parent - this.offset.relative.top - - - // The offsetParent's offset without borders (offset + border) - this.offset.parent.top + - ( ( this.cssPosition === "fixed" ? - -this.scrollParent.scrollTop() : - ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) ) - ), - left: ( - - // The absolute mouse position - pageX - - - // Click offset (relative to the element) - this.offset.click.left - - - // Only for relative positioned nodes: Relative offset from element to offset parent - this.offset.relative.left - - - // The offsetParent's offset without borders (offset + border) - this.offset.parent.left + - ( ( this.cssPosition === "fixed" ? - -this.scrollParent.scrollLeft() : - scrollIsRootNode ? 0 : scroll.scrollLeft() ) ) - ) - }; - - }, - - _rearrange: function( event, i, a, hardRefresh ) { - - a ? a[ 0 ].appendChild( this.placeholder[ 0 ] ) : - i.item[ 0 ].parentNode.insertBefore( this.placeholder[ 0 ], - ( this.direction === "down" ? i.item[ 0 ] : i.item[ 0 ].nextSibling ) ); - - //Various things done here to improve the performance: - // 1. we create a setTimeout, that calls refreshPositions - // 2. on the instance, we have a counter variable, that get's higher after every append - // 3. on the local scope, we copy the counter variable, and check in the timeout, - // if it's still the same - // 4. this lets only the last addition to the timeout stack through - this.counter = this.counter ? ++this.counter : 1; - var counter = this.counter; - - this._delay( function() { - if ( counter === this.counter ) { - - //Precompute after each DOM insertion, NOT on mousemove - this.refreshPositions( !hardRefresh ); - } - } ); - - }, - - _clear: function( event, noPropagation ) { - - this.reverting = false; - - // We delay all events that have to be triggered to after the point where the placeholder - // has been removed and everything else normalized again - var i, - delayedTriggers = []; - - // We first have to update the dom position of the actual currentItem - // Note: don't do it if the current item is already removed (by a user), or it gets - // reappended (see #4088) - if ( !this._noFinalSort && this.currentItem.parent().length ) { - this.placeholder.before( this.currentItem ); - } - this._noFinalSort = null; - - if ( this.helper[ 0 ] === this.currentItem[ 0 ] ) { - for ( i in this._storedCSS ) { - if ( this._storedCSS[ i ] === "auto" || this._storedCSS[ i ] === "static" ) { - this._storedCSS[ i ] = ""; - } - } - this.currentItem.css( this._storedCSS ); - this._removeClass( this.currentItem, "ui-sortable-helper" ); - } else { - this.currentItem.show(); - } - - if ( this.fromOutside && !noPropagation ) { - delayedTriggers.push( function( event ) { - this._trigger( "receive", event, this._uiHash( this.fromOutside ) ); - } ); - } - if ( ( this.fromOutside || - this.domPosition.prev !== - this.currentItem.prev().not( ".ui-sortable-helper" )[ 0 ] || - this.domPosition.parent !== this.currentItem.parent()[ 0 ] ) && !noPropagation ) { - - // Trigger update callback if the DOM position has changed - delayedTriggers.push( function( event ) { - this._trigger( "update", event, this._uiHash() ); - } ); - } - - // Check if the items Container has Changed and trigger appropriate - // events. - if ( this !== this.currentContainer ) { - if ( !noPropagation ) { - delayedTriggers.push( function( event ) { - this._trigger( "remove", event, this._uiHash() ); - } ); - delayedTriggers.push( ( function( c ) { - return function( event ) { - c._trigger( "receive", event, this._uiHash( this ) ); - }; - } ).call( this, this.currentContainer ) ); - delayedTriggers.push( ( function( c ) { - return function( event ) { - c._trigger( "update", event, this._uiHash( this ) ); - }; - } ).call( this, this.currentContainer ) ); - } - } - - //Post events to containers - function delayEvent( type, instance, container ) { - return function( event ) { - container._trigger( type, event, instance._uiHash( instance ) ); - }; - } - for ( i = this.containers.length - 1; i >= 0; i-- ) { - if ( !noPropagation ) { - delayedTriggers.push( delayEvent( "deactivate", this, this.containers[ i ] ) ); - } - if ( this.containers[ i ].containerCache.over ) { - delayedTriggers.push( delayEvent( "out", this, this.containers[ i ] ) ); - this.containers[ i ].containerCache.over = 0; - } - } - - //Do what was originally in plugins - if ( this.storedCursor ) { - this.document.find( "body" ).css( "cursor", this.storedCursor ); - this.storedStylesheet.remove(); - } - if ( this._storedOpacity ) { - this.helper.css( "opacity", this._storedOpacity ); - } - if ( this._storedZIndex ) { - this.helper.css( "zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex ); - } - - this.dragging = false; - - if ( !noPropagation ) { - this._trigger( "beforeStop", event, this._uiHash() ); - } - - //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, - // it unbinds ALL events from the original node! - this.placeholder[ 0 ].parentNode.removeChild( this.placeholder[ 0 ] ); - - if ( !this.cancelHelperRemoval ) { - if ( this.helper[ 0 ] !== this.currentItem[ 0 ] ) { - this.helper.remove(); - } - this.helper = null; - } - - if ( !noPropagation ) { - for ( i = 0; i < delayedTriggers.length; i++ ) { - - // Trigger all delayed events - delayedTriggers[ i ].call( this, event ); - } - this._trigger( "stop", event, this._uiHash() ); - } - - this.fromOutside = false; - return !this.cancelHelperRemoval; - - }, - - _trigger: function() { - if ( $.Widget.prototype._trigger.apply( this, arguments ) === false ) { - this.cancel(); - } - }, - - _uiHash: function( _inst ) { - var inst = _inst || this; - return { - helper: inst.helper, - placeholder: inst.placeholder || $( [] ), - position: inst.position, - originalPosition: inst.originalPosition, - offset: inst.positionAbs, - item: inst.currentItem, - sender: _inst ? _inst.element : null - }; - } - -} ); - - /*! * jQuery UI Menu 1.12.1 * http://jqueryui.com diff --git a/addons/web/static/src/core/utils/draggable_hook_builder.js b/addons/web/static/src/core/utils/draggable_hook_builder.js index d769f513c7e..d7ab14638a2 100644 --- a/addons/web/static/src/core/utils/draggable_hook_builder.js +++ b/addons/web/static/src/core/utils/draggable_hook_builder.js @@ -81,7 +81,7 @@ import { hasTouch, isBrowserFirefox, isIOS } from "../browser/feature_detection" */ const DRAGGABLE_CLASS = "o_draggable"; -const DRAGGED_CLASS = "o_dragged"; +export const DRAGGED_CLASS = "o_dragged"; const DEFAULT_ACCEPTED_PARAMS = { enable: [Boolean, Function], diff --git a/addons/web/static/src/core/utils/sortable.js b/addons/web/static/src/core/utils/sortable.js index e536aef644c..5dc98abd8a1 100644 --- a/addons/web/static/src/core/utils/sortable.js +++ b/addons/web/static/src/core/utils/sortable.js @@ -1,6 +1,9 @@ /** @odoo-module **/ -import { makeDraggableHook as nativeMakeDraggableHook } from "@web/core/utils/draggable_hook_builder"; +import { + DRAGGED_CLASS, + makeDraggableHook as nativeMakeDraggableHook, +} from "@web/core/utils/draggable_hook_builder"; import { pick } from "@web/core/utils/objects"; /** @typedef {import("@web/core/utils/draggable_hook_builder").DraggableHandlerParams} DraggableHandlerParams */ @@ -28,6 +31,10 @@ import { pick } from "@web/core/utils/objects"; * @property {boolean | (() => boolean)} [connectGroups] whether elements can be dragged * accross different parent groups. Note that it requires a `groups` param to work. * @property {string | (() => string)} [cursor] cursor style during the dragging sequence. + * @property {Boolean} [clone] the placeholder is a clone of the drag element. + * @property {Array} [placeholderClasses] array of classes added to the placeholder element. + * @property {Boolean} [applyChangeOnDrop] on drop the change is applied to the DOM. + * @property {Array} [followingElementClasses] array of classes added to the element that follow the pointer. * * HANDLERS (also optional) * @@ -69,11 +76,19 @@ const hookParams = { acceptedParams: { groups: [String, Function], connectGroups: [Boolean, Function], + clone: [Boolean], + placeholderClasses: [Object], + applyChangeOnDrop: [Boolean], + followingElementClasses: [Object], }, defaultParams: { connectGroups: false, edgeScrolling: { speed: 20, threshold: 60 }, groupSelector: null, + clone: true, + placeholderClasses: [], + applyChangeOnDrop: false, + followingElementClasses: [], }, // Build steps @@ -86,6 +101,11 @@ const hookParams = { // Connection accross groups ctx.connectGroups = params.connectGroups; + + ctx.placeholderClone = params.clone; + ctx.placeholderClasses = params.placeholderClasses; + ctx.applyChangeOnDrop = params.applyChangeOnDrop; + ctx.followingElementClasses = params.followingElementClasses; }, // Runtime steps @@ -120,6 +140,65 @@ const hookParams = { callHandler("onElementLeave", { element }); }; + const onElementComplexPointerEnter = (ev) => { + const element = ev.currentTarget; + + const siblingArray = [...element.parentElement.children].filter( + (el) => + el === current.placeHolder || + (el.matches(elementSelector) && !el.classList.contains(DRAGGED_CLASS)) + ); + const elementIndex = siblingArray.indexOf(element); + const placeholderIndex = siblingArray.indexOf(current.placeHolder); + const isDirectSibling = Math.abs(elementIndex - placeholderIndex) === 1; + if ( + connectGroups || + !groupSelector || + current.group === element.closest(groupSelector) + ) { + const pos = current.placeHolder.compareDocumentPosition(element); + if (isDirectSibling) { + if (pos === Node.DOCUMENT_POSITION_PRECEDING) { + element.before(current.placeHolder); + } else if (pos === Node.DOCUMENT_POSITION_FOLLOWING) { + element.after(current.placeHolder); + } + } else { + if (pos === Node.DOCUMENT_POSITION_FOLLOWING) { + element.before(current.placeHolder); + } else if (pos === Node.DOCUMENT_POSITION_PRECEDING) { + element.after(current.placeHolder); + } + } + } + callHandler("onElementEnter", { element }); + }; + + /** + * Element "pointerleave" event handler. + * @param {PointerEvent} ev + */ + const onElementComplexPointerLeave = (ev) => { + const element = ev.currentTarget; + + const siblingArray = [...element.parentElement.children].filter( + (el) => + el === current.placeHolder || + (el.matches(elementSelector) && !el.classList.contains(DRAGGED_CLASS)) + ); + const elementIndex = siblingArray.indexOf(element); + const isAtEdge = elementIndex === 0 || elementIndex === siblingArray.length - 1; + if (isAtEdge && (!groupSelector || current.group === element.closest(groupSelector))) { + const pos = current.placeHolder.compareDocumentPosition(element); + if (pos === Node.DOCUMENT_POSITION_PRECEDING) { + element.before(current.placeHolder); + } else if (pos === Node.DOCUMENT_POSITION_FOLLOWING) { + element.after(current.placeHolder); + } + } + callHandler("onElementLeave", { element }); + }; + /** * Group "pointerenter" event handler. * @param {PointerEvent} ev @@ -140,15 +219,17 @@ const hookParams = { }; const { connectGroups, current, elementSelector, groupSelector, ref } = ctx; - const { width, height } = current.elementRect; + if (ctx.placeholderClone) { + const { width, height } = current.elementRect; - // Adjusts size for the placeholder element - addStyle(current.placeHolder, { - visibility: "hidden", - display: "block", - width: `${width}px`, - height: `${height}px`, - }); + // Adjusts size for the placeholder element + addStyle(current.placeHolder, { + visibility: "hidden", + display: "block", + width: `${width}px`, + height: `${height}px`, + }); + } // Binds handlers on eligible groups, if the elements are not confined to // their parents and a 'groupSelector' has been provided. @@ -162,8 +243,13 @@ const hookParams = { // Binds handlers on eligible elements for (const siblingEl of ref.el.querySelectorAll(elementSelector)) { if (siblingEl !== current.element && siblingEl !== current.placeHolder) { - addListener(siblingEl, "pointerenter", onElementPointerEnter); - addListener(siblingEl, "pointerleave", onElementPointerLeave); + if (ctx.placeholderClone) { + addListener(siblingEl, "pointerenter", onElementPointerEnter); + addListener(siblingEl, "pointerleave", onElementPointerLeave); + } else { + addListener(siblingEl, "pointerenter", onElementComplexPointerEnter); + addListener(siblingEl, "pointerleave", onElementComplexPointerLeave); + } } } @@ -180,8 +266,17 @@ const hookParams = { const previous = current.placeHolder.previousElementSibling; const next = current.placeHolder.nextElementSibling; if (previous !== current.element && next !== current.element) { + const element = current.element; + if (ctx.applyChangeOnDrop) { + // Apply to the DOM the result of sortable() + if (previous) { + previous.after(element); + } else if (next) { + next.before(element); + } + } return { - element: current.element, + element, group: current.group, previous, next, @@ -199,8 +294,15 @@ const hookParams = { } } - current.placeHolder = current.element.cloneNode(false); + if (ctx.placeholderClone) { + current.placeHolder = current.element.cloneNode(false); + } else { + current.placeHolder = document.createElement("div"); + } + current.placeHolder.classList.add(...ctx.placeholderClasses); + current.element.classList.add(...ctx.followingElementClasses); + addCleanup(() => current.element.classList.remove(...ctx.followingElementClasses)); addCleanup(() => current.placeHolder.remove()); return pick(current, "element", "group"); diff --git a/addons/web/static/src/core/utils/sortable_service.js b/addons/web/static/src/core/utils/sortable_service.js new file mode 100644 index 00000000000..1cea25fb424 --- /dev/null +++ b/addons/web/static/src/core/utils/sortable_service.js @@ -0,0 +1,98 @@ +/** @odoo-module **/ + +import { registry } from "../registry"; +import { useSortable } from "@web/core/utils/sortable"; +import { throttleForAnimation } from "@web/core/utils/timing"; +import { reactive } from "@odoo/owl"; + +/** + * @typedef SortableServiceHookParams + * @extends SortableParams + * @property {{el: HTMLElement} | ReturnType} [ref] container of sortable + * @property {string | Symbol} [sortableId] identifier when multiple sortable on the same container + */ + +const DEFAULT_SORTABLE_ID = Symbol.for("defaultSortable"); +export const sortableService = { + start() { + /** + * Map to avoid to setup/enable twice or more time the same element + * @type {Map} + */ + const boundElements = new Map(); + return { + /** + * @param {SortableServiceHookParams} hookParams + */ + create: (hookParams) => { + const element = hookParams.ref.el; + const sortableId = hookParams.sortableId ?? DEFAULT_SORTABLE_ID; + if (boundElements.has(element)) { + const boundElement = boundElements.get(element); + if (sortableId in boundElement) { + return { + enable() { + return { + cleanup: boundElement[sortableId], + }; + }, + }; + } + } + /** + * @type {Map} + */ + const setupFunctions = new Map(); + /** + * @type {Array} + */ + const cleanupFunctions = []; + + const cleanup = () => { + const boundElement = boundElements.get(element); + if (sortableId in boundElement) { + delete boundElement[sortableId]; + if (boundElement.length === 0) { + boundElements.delete(element); + } + } + cleanupFunctions.forEach((fn) => fn()); + }; + + // Setup hookParam + const setupHooks = { + wrapState: reactive, + throttle: throttleForAnimation, + addListener: (el, type, listener) => { + el.addEventListener(type, listener); + cleanupFunctions.push(() => el.removeEventListener(type, listener)); + }, + setup: (setupFn, dependenciesFn) => setupFunctions.set(setupFn, dependenciesFn), + teardown: (fn) => cleanupFunctions.push(fn), + }; + + useSortable({ setupHooks, ...hookParams }); + + const boundElement = boundElements.get(element); + if (boundElement) { + boundElement[sortableId] = cleanup; + } else { + boundElements.set(element, { [sortableId]: cleanup }); + } + + return { + enable() { + setupFunctions.forEach((dependenciesFn, setupFn) => + setupFn(...dependenciesFn()) + ); + return { + cleanup, + }; + }, + }; + }, + }; + }, +}; + +registry.category("services").add("sortable", sortableService); diff --git a/addons/web/static/tests/core/utils/sortable_tests.js b/addons/web/static/tests/core/utils/sortable_tests.js index e563fd2f232..55b6c9c37c9 100644 --- a/addons/web/static/tests/core/utils/sortable_tests.js +++ b/addons/web/static/tests/core/utils/sortable_tests.js @@ -498,4 +498,115 @@ QUnit.module("Draggable", ({ beforeEach }) => { assert.verifySteps([]); }); + + QUnit.test("the classes parameters (placeholderElement, helpElement)", async (assert) => { + assert.expect(7); + + let dragElement; + + class List extends Component { + setup() { + useSortable({ + ref: useRef("root"), + elements: ".item", + placeholderClasses: ["placeholder-t1", "placeholder-t2"], + followingElementClasses: ["add-1", "add-2"], + onDragStart({ element }) { + dragElement = element; + assert.hasClass(dragElement, "add-1"); + assert.hasClass(dragElement, "add-2"); + // the placeholder is added in onDragStart after the current element + const children = [...dragElement.parentElement.children]; + const placeholder = children[children.indexOf(dragElement) + 1]; + assert.hasClass(placeholder, "placeholder-t1"); + assert.hasClass(placeholder, "placeholder-t2"); + }, + }); + } + } + + List.template = xml` +
+
    +
  • +
+
`; + + await mount(List, target); + // First item after 2nd item + const { drop, moveTo } = await drag(".item:first-child"); + await moveTo(".item:nth-child(2)"); + await drop(); + assert.doesNotHaveClass(dragElement, "add-1"); + assert.doesNotHaveClass(dragElement, "add-2"); + assert.containsNone(target, ".item.placeholder-t1.placeholder-t2"); + }); + + QUnit.test("applyChangeOnDrop option", async (assert) => { + assert.expect(2); + + class List extends Component { + setup() { + useSortable({ + ref: useRef("root"), + elements: ".item", + placeholderClasses: ["placeholder"], + applyChangeOnDrop: true, + onDragStart({ element }) { + const items = [...target.querySelectorAll(".item:not(.placeholder)")]; + assert.strictEqual(items.map((el) => el.innerText).toString(), "1,2,3"); + }, + onDragEnd({ element, group }) { + const items = [...target.querySelectorAll(".item:not(.placeholder)")]; + assert.strictEqual(items.map((el) => el.innerText).toString(), "2,1,3"); + }, + }); + } + } + + List.template = xml` +
+
    +
  • +
+
`; + + await mount(List, target); + // First item after 2nd item + const { drop, moveTo } = await drag(".item:first-child"); + await moveTo(".item:nth-child(2)"); + await drop(); + }); + + QUnit.test("clone option", async (assert) => { + assert.expect(2); + + class List extends Component { + setup() { + useSortable({ + ref: useRef("root"), + elements: ".item", + placeholderClasses: ["placeholder"], + clone: false, + onDragStart({ element }) { + assert.containsOnce(target, ".placeholder:not(.item)"); + }, + }); + } + } + + List.template = xml` +
+
    +
  • +
+
`; + + await mount(List, target); + // First item after 2nd item + const { drop, moveTo } = await drag(".item:first-child"); + await moveTo(".item:nth-child(2)"); + await drop(); + assert.containsNone(target, ".placeholder:not(.item)"); + }); }); diff --git a/addons/web_editor/static/src/js/editor/snippets.options.js b/addons/web_editor/static/src/js/editor/snippets.options.js index 660902f7052..a38f8ae26bb 100644 --- a/addons/web_editor/static/src/js/editor/snippets.options.js +++ b/addons/web_editor/static/src/js/editor/snippets.options.js @@ -2008,6 +2008,14 @@ const ListUserValueWidget = UserValueWidget.extend({ } }, + /** + * @override + */ + destroy() { + this.bindedSortable?.cleanup(); + this._super(...arguments); + }, + //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- @@ -2170,16 +2178,18 @@ const ListUserValueWidget = UserValueWidget.extend({ if (this.el.dataset.unsortable) { return; } - $(this.listTable).sortable({ - axis: 'y', - handle: '.o_we_drag_handle', - items: 'tr', - cursor: 'move', - opacity: 0.6, - stop: (event, ui) => { - this._notifyCurrentState(); + this.bindedSortable = this.call( + "sortable", + "create", + { + ref: { el: this.listTable }, + elements: "tr", + followingElementClasses: ["opacity-50"], + handle: ".o_we_drag_handle", + onDrop: () => this._notifyCurrentState(), + applyChangeOnDrop: true, }, - }); + ).enable(); }, /** * @private diff --git a/addons/website/static/tests/tours/snippet_social_media.js b/addons/website/static/tests/tours/snippet_social_media.js index 55ba9dd152a..380e1fe9a7c 100644 --- a/addons/website/static/tests/tours/snippet_social_media.js +++ b/addons/website/static/tests/tours/snippet_social_media.js @@ -77,7 +77,7 @@ wTourUtils.registerWebsitePreviewTour('snippet_social_media', { content: 'Drag the facebook link at the end of the list', trigger: 'we-list table we-button.o_we_drag_handle', position: 'bottom', - run: "drag_and_drop we-list table tr:last-child", + run: "drag_and_drop_native we-list table tr:last-child", }, { content: 'Check drop completed', diff --git a/addons/website_slides/static/src/js/slides_course_quiz.js b/addons/website_slides/static/src/js/slides_course_quiz.js index 230d4a1b6e6..bc783ae38bb 100644 --- a/addons/website_slides/static/src/js/slides_course_quiz.js +++ b/addons/website_slides/static/src/js/slides_course_quiz.js @@ -111,6 +111,11 @@ }); }, + destroy() { + this._unbindSortable(); + return this._super(...arguments); + }, + //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- @@ -141,12 +146,23 @@ * @private */ _bindSortable: function () { - this.$el.sortable({ - handle: '.o_wslides_js_quiz_sequence_handler', - items: '.o_wslides_js_lesson_quiz_question', - stop: this._reorderQuestions.bind(this), - placeholder: 'o_wslides_js_quiz_sequence_highlight position-relative my-3' - }); + this.bindedSortable = this.call( + "sortable", + "create", + { + ref: { el: this.el }, + handle: ".o_wslides_js_quiz_sequence_handler", + elements: ".o_wslides_js_lesson_quiz_question", + onDrop: this._reorderQuestions.bind(this), + clone: false, + placeholderClasses: ['o_wslides_js_quiz_sequence_highlight', 'position-relative', 'my-3'], + applyChangeOnDrop: true + }, + ).enable(); + }, + + _unbindSortable: function () { + this.bindedSortable?.cleanup(); }, /** diff --git a/addons/website_slides/static/src/js/slides_course_slides_list.js b/addons/website_slides/static/src/js/slides_course_slides_list.js index 000fdc44659..c92686ac24d 100644 --- a/addons/website_slides/static/src/js/slides_course_slides_list.js +++ b/addons/website_slides/static/src/js/slides_course_slides_list.js @@ -11,11 +11,17 @@ publicWidget.registry.websiteSlidesCourseSlidesList = SlideCoursePage.extend({ this._super.apply(this,arguments); this.channelId = this.$el.data('channelId'); + this.bindedSortable = []; this._updateHref(); this._bindSortable(); }, + destroy() { + this._unbindSortable(); + return this._super(...arguments); + }, + //-------------------------------------------------------------------------- // Private //--------------------------------------------------------------------------, @@ -28,20 +34,43 @@ publicWidget.registry.websiteSlidesCourseSlidesList = SlideCoursePage.extend({ * @private */ _bindSortable: function () { - this.$('ul.o_wslides_js_slides_list_container').sortable({ - handle: '.o_wslides_slides_list_drag', - stop: this._reorderSlides.bind(this), - items: '.o_wslides_slide_list_category', - placeholder: 'o_wslides_slides_list_slide_hilight position-relative mb-1' - }); + const sortableBaseParam = { + clone: false, + placeholderClasses: ['o_wslides_slides_list_slide_hilight', 'position-relative', 'mb-1'], + onDrop: this._reorderSlides.bind(this), + applyChangeOnDrop: true + }; - this.$('.o_wslides_js_slides_list_container ul').sortable({ - handle: '.o_wslides_slides_list_drag', - connectWith: '.o_wslides_js_slides_list_container ul', - stop: this._reorderSlides.bind(this), - items: '.o_wslides_slides_list_slide:not(.o_wslides_js_slides_list_empty)', - placeholder: 'o_wslides_slides_list_slide_hilight position-relative mb-1' - }); + const container = this.el.querySelector('ul.o_wslides_js_slides_list_container'); + this.bindedSortable.push(this.call( + "sortable", + "create", + { + ...sortableBaseParam, + ref: { el: container }, + elements: ".o_wslides_slide_list_category", + handle: ".o_wslides_slide_list_category_header .o_wslides_slides_list_drag", + sortableId: "category", + }, + ).enable()); + + this.bindedSortable.push(this.call( + "sortable", + "create", + { + ...sortableBaseParam, + ref: { el: container }, + elements: ".o_wslides_slides_list_slide:not(.o_wslides_js_slides_list_empty):not(.o_not_editable)", + handle: ".o_wslides_slides_list_drag", + connectGroups: true, + groups: ".o_wslides_js_slides_list_container ul", + sortableId: "list", + }, + ).enable()); + }, + + _unbindSortable: function () { + this.bindedSortable.forEach(sortable => sortable.cleanup()); }, /**