mirror of
https://github.com/jquery/jquery-ui.git
synced 2024-11-21 11:04:24 +00:00
8c66934434
I was running into a problem with a popup menu control in a dialog; clicks weren't working (but keyboard was working fine). It turned out that the menu was getting destroyed before the click event could fire. Tracked down the issue to the way draggable blurs focused controls; it was doing the blur before it ran through the logic to figure out if the drag was actually on the handle. I've moved the blur below these checks, so it'll only blur things if it actually needs to handle the drag. Otherwise, it asserts no opinion on what should and shouldn't be focused, which seems like the way things ought to be. Also, added a unit test to check for the expected behavior. Fixes #15046 Closes gh-1730
1251 lines
35 KiB
JavaScript
1251 lines
35 KiB
JavaScript
/*!
|
|
* jQuery UI Draggable @VERSION
|
|
* http://jqueryui.com
|
|
*
|
|
* Copyright jQuery Foundation and other contributors
|
|
* Released under the MIT license.
|
|
* http://jquery.org/license
|
|
*/
|
|
|
|
//>>label: Draggable
|
|
//>>group: Interactions
|
|
//>>description: Enables dragging functionality for any element.
|
|
//>>docs: http://api.jqueryui.com/draggable/
|
|
//>>demos: http://jqueryui.com/draggable/
|
|
//>>css.structure: ../../themes/base/draggable.css
|
|
|
|
( function( factory ) {
|
|
if ( typeof define === "function" && define.amd ) {
|
|
|
|
// AMD. Register as an anonymous module.
|
|
define( [
|
|
"jquery",
|
|
"./mouse",
|
|
"../data",
|
|
"../plugin",
|
|
"../safe-active-element",
|
|
"../safe-blur",
|
|
"../scroll-parent",
|
|
"../version",
|
|
"../widget"
|
|
], factory );
|
|
} else {
|
|
|
|
// Browser globals
|
|
factory( jQuery );
|
|
}
|
|
}( function( $ ) {
|
|
|
|
$.widget( "ui.draggable", $.ui.mouse, {
|
|
version: "@VERSION",
|
|
widgetEventPrefix: "drag",
|
|
options: {
|
|
addClasses: true,
|
|
appendTo: "parent",
|
|
axis: false,
|
|
connectToSortable: false,
|
|
containment: false,
|
|
cursor: "auto",
|
|
cursorAt: false,
|
|
grid: false,
|
|
handle: false,
|
|
helper: "original",
|
|
iframeFix: false,
|
|
opacity: false,
|
|
refreshPositions: false,
|
|
revert: false,
|
|
revertDuration: 500,
|
|
scope: "default",
|
|
scroll: true,
|
|
scrollSensitivity: 20,
|
|
scrollSpeed: 20,
|
|
snap: false,
|
|
snapMode: "both",
|
|
snapTolerance: 20,
|
|
stack: false,
|
|
zIndex: false,
|
|
|
|
// Callbacks
|
|
drag: null,
|
|
start: null,
|
|
stop: null
|
|
},
|
|
_create: function() {
|
|
|
|
if ( this.options.helper === "original" ) {
|
|
this._setPositionRelative();
|
|
}
|
|
if ( this.options.addClasses ) {
|
|
this._addClass( "ui-draggable" );
|
|
}
|
|
this._setHandleClassName();
|
|
|
|
this._mouseInit();
|
|
},
|
|
|
|
_setOption: function( key, value ) {
|
|
this._super( key, value );
|
|
if ( key === "handle" ) {
|
|
this._removeHandleClassName();
|
|
this._setHandleClassName();
|
|
}
|
|
},
|
|
|
|
_destroy: function() {
|
|
if ( ( this.helper || this.element ).is( ".ui-draggable-dragging" ) ) {
|
|
this.destroyOnClear = true;
|
|
return;
|
|
}
|
|
this._removeHandleClassName();
|
|
this._mouseDestroy();
|
|
},
|
|
|
|
_mouseCapture: function( event ) {
|
|
var o = this.options;
|
|
|
|
// Among others, prevent a drag on a resizable-handle
|
|
if ( this.helper || o.disabled ||
|
|
$( event.target ).closest( ".ui-resizable-handle" ).length > 0 ) {
|
|
return false;
|
|
}
|
|
|
|
//Quit if we're not on a valid handle
|
|
this.handle = this._getHandle( event );
|
|
if ( !this.handle ) {
|
|
return false;
|
|
}
|
|
|
|
this._blurActiveElement( event );
|
|
|
|
this._blockFrames( o.iframeFix === true ? "iframe" : o.iframeFix );
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
_blockFrames: function( selector ) {
|
|
this.iframeBlocks = this.document.find( selector ).map( function() {
|
|
var iframe = $( this );
|
|
|
|
return $( "<div>" )
|
|
.css( "position", "absolute" )
|
|
.appendTo( iframe.parent() )
|
|
.outerWidth( iframe.outerWidth() )
|
|
.outerHeight( iframe.outerHeight() )
|
|
.offset( iframe.offset() )[ 0 ];
|
|
} );
|
|
},
|
|
|
|
_unblockFrames: function() {
|
|
if ( this.iframeBlocks ) {
|
|
this.iframeBlocks.remove();
|
|
delete this.iframeBlocks;
|
|
}
|
|
},
|
|
|
|
_blurActiveElement: function( event ) {
|
|
var activeElement = $.ui.safeActiveElement( this.document[ 0 ] ),
|
|
target = $( event.target );
|
|
|
|
// Don't blur if the event occurred on an element that is within
|
|
// the currently focused element
|
|
// See #10527, #12472
|
|
if ( target.closest( activeElement ).length ) {
|
|
return;
|
|
}
|
|
|
|
// Blur any element that currently has focus, see #4261
|
|
$.ui.safeBlur( activeElement );
|
|
},
|
|
|
|
_mouseStart: function( event ) {
|
|
|
|
var o = this.options;
|
|
|
|
//Create and append the visible helper
|
|
this.helper = this._createHelper( event );
|
|
|
|
this._addClass( this.helper, "ui-draggable-dragging" );
|
|
|
|
//Cache the helper size
|
|
this._cacheHelperProportions();
|
|
|
|
//If ddmanager is used for droppables, set the global draggable
|
|
if ( $.ui.ddmanager ) {
|
|
$.ui.ddmanager.current = this;
|
|
}
|
|
|
|
/*
|
|
* - Position generation -
|
|
* This block generates everything position related - it's the core of draggables.
|
|
*/
|
|
|
|
//Cache the margins of the original element
|
|
this._cacheMargins();
|
|
|
|
//Store the helper's css position
|
|
this.cssPosition = this.helper.css( "position" );
|
|
this.scrollParent = this.helper.scrollParent( true );
|
|
this.offsetParent = this.helper.offsetParent();
|
|
this.hasFixedAncestor = this.helper.parents().filter( function() {
|
|
return $( this ).css( "position" ) === "fixed";
|
|
} ).length > 0;
|
|
|
|
//The element's absolute position on the page minus margins
|
|
this.positionAbs = this.element.offset();
|
|
this._refreshOffsets( event );
|
|
|
|
//Generate the original position
|
|
this.originalPosition = this.position = this._generatePosition( event, false );
|
|
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 ) );
|
|
|
|
//Set a containment if given in the options
|
|
this._setContainment();
|
|
|
|
//Trigger event + callbacks
|
|
if ( this._trigger( "start", event ) === false ) {
|
|
this._clear();
|
|
return false;
|
|
}
|
|
|
|
//Recache the helper size
|
|
this._cacheHelperProportions();
|
|
|
|
//Prepare the droppable offsets
|
|
if ( $.ui.ddmanager && !o.dropBehaviour ) {
|
|
$.ui.ddmanager.prepareOffsets( this, event );
|
|
}
|
|
|
|
// Execute the drag once - this causes the helper not to be visible before getting its
|
|
// correct position
|
|
this._mouseDrag( event, true );
|
|
|
|
// If the ddmanager is used for droppables, inform the manager that dragging has started
|
|
// (see #5003)
|
|
if ( $.ui.ddmanager ) {
|
|
$.ui.ddmanager.dragStart( this, event );
|
|
}
|
|
|
|
return true;
|
|
},
|
|
|
|
_refreshOffsets: function( event ) {
|
|
this.offset = {
|
|
top: this.positionAbs.top - this.margins.top,
|
|
left: this.positionAbs.left - this.margins.left,
|
|
scroll: false,
|
|
parent: this._getParentOffset(),
|
|
relative: this._getRelativeOffset()
|
|
};
|
|
|
|
this.offset.click = {
|
|
left: event.pageX - this.offset.left,
|
|
top: event.pageY - this.offset.top
|
|
};
|
|
},
|
|
|
|
_mouseDrag: function( event, noPropagation ) {
|
|
|
|
// reset any necessary cached properties (see #5009)
|
|
if ( this.hasFixedAncestor ) {
|
|
this.offset.parent = this._getParentOffset();
|
|
}
|
|
|
|
//Compute the helpers position
|
|
this.position = this._generatePosition( event, true );
|
|
this.positionAbs = this._convertPositionTo( "absolute" );
|
|
|
|
//Call plugins and callbacks and use the resulting position if something is returned
|
|
if ( !noPropagation ) {
|
|
var ui = this._uiHash();
|
|
if ( this._trigger( "drag", event, ui ) === false ) {
|
|
this._mouseUp( new $.Event( "mouseup", event ) );
|
|
return false;
|
|
}
|
|
this.position = ui.position;
|
|
}
|
|
|
|
this.helper[ 0 ].style.left = this.position.left + "px";
|
|
this.helper[ 0 ].style.top = this.position.top + "px";
|
|
|
|
if ( $.ui.ddmanager ) {
|
|
$.ui.ddmanager.drag( this, event );
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
_mouseStop: function( event ) {
|
|
|
|
//If we are using droppables, inform the manager about the drop
|
|
var that = this,
|
|
dropped = false;
|
|
if ( $.ui.ddmanager && !this.options.dropBehaviour ) {
|
|
dropped = $.ui.ddmanager.drop( this, event );
|
|
}
|
|
|
|
//if a drop comes from outside (a sortable)
|
|
if ( this.dropped ) {
|
|
dropped = this.dropped;
|
|
this.dropped = false;
|
|
}
|
|
|
|
if ( ( this.options.revert === "invalid" && !dropped ) ||
|
|
( this.options.revert === "valid" && dropped ) ||
|
|
this.options.revert === true || ( $.isFunction( this.options.revert ) &&
|
|
this.options.revert.call( this.element, dropped ) )
|
|
) {
|
|
$( this.helper ).animate(
|
|
this.originalPosition,
|
|
parseInt( this.options.revertDuration, 10 ),
|
|
function() {
|
|
if ( that._trigger( "stop", event ) !== false ) {
|
|
that._clear();
|
|
}
|
|
}
|
|
);
|
|
} else {
|
|
if ( this._trigger( "stop", event ) !== false ) {
|
|
this._clear();
|
|
}
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
_mouseUp: function( event ) {
|
|
this._unblockFrames();
|
|
|
|
// If the ddmanager is used for droppables, inform the manager that dragging has stopped
|
|
// (see #5003)
|
|
if ( $.ui.ddmanager ) {
|
|
$.ui.ddmanager.dragStop( this, event );
|
|
}
|
|
|
|
// Only need to focus if the event occurred on the draggable itself, see #10527
|
|
if ( this.handleElement.is( event.target ) ) {
|
|
|
|
// The interaction is over; whether or not the click resulted in a drag,
|
|
// focus the element
|
|
this.element.trigger( "focus" );
|
|
}
|
|
|
|
return $.ui.mouse.prototype._mouseUp.call( this, event );
|
|
},
|
|
|
|
cancel: function() {
|
|
|
|
if ( this.helper.is( ".ui-draggable-dragging" ) ) {
|
|
this._mouseUp( new $.Event( "mouseup", { target: this.element[ 0 ] } ) );
|
|
} else {
|
|
this._clear();
|
|
}
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
_getHandle: function( event ) {
|
|
return this.options.handle ?
|
|
!!$( event.target ).closest( this.element.find( this.options.handle ) ).length :
|
|
true;
|
|
},
|
|
|
|
_setHandleClassName: function() {
|
|
this.handleElement = this.options.handle ?
|
|
this.element.find( this.options.handle ) : this.element;
|
|
this._addClass( this.handleElement, "ui-draggable-handle" );
|
|
},
|
|
|
|
_removeHandleClassName: function() {
|
|
this._removeClass( this.handleElement, "ui-draggable-handle" );
|
|
},
|
|
|
|
_createHelper: function( event ) {
|
|
|
|
var o = this.options,
|
|
helperIsFunction = $.isFunction( o.helper ),
|
|
helper = helperIsFunction ?
|
|
$( o.helper.apply( this.element[ 0 ], [ event ] ) ) :
|
|
( o.helper === "clone" ?
|
|
this.element.clone().removeAttr( "id" ) :
|
|
this.element );
|
|
|
|
if ( !helper.parents( "body" ).length ) {
|
|
helper.appendTo( ( o.appendTo === "parent" ?
|
|
this.element[ 0 ].parentNode :
|
|
o.appendTo ) );
|
|
}
|
|
|
|
// Http://bugs.jqueryui.com/ticket/9446
|
|
// a helper function can return the original element
|
|
// which wouldn't have been set to relative in _create
|
|
if ( helperIsFunction && helper[ 0 ] === this.element[ 0 ] ) {
|
|
this._setPositionRelative();
|
|
}
|
|
|
|
if ( helper[ 0 ] !== this.element[ 0 ] &&
|
|
!( /(fixed|absolute)/ ).test( helper.css( "position" ) ) ) {
|
|
helper.css( "position", "absolute" );
|
|
}
|
|
|
|
return helper;
|
|
|
|
},
|
|
|
|
_setPositionRelative: function() {
|
|
if ( !( /^(?:r|a|f)/ ).test( this.element.css( "position" ) ) ) {
|
|
this.element[ 0 ].style.position = "relative";
|
|
}
|
|
},
|
|
|
|
_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;
|
|
}
|
|
},
|
|
|
|
_isRootNode: function( element ) {
|
|
return ( /(html|body)/i ).test( element.tagName ) || element === this.document[ 0 ];
|
|
},
|
|
|
|
_getParentOffset: function() {
|
|
|
|
//Get the offsetParent and cache its position
|
|
var po = this.offsetParent.offset(),
|
|
document = this.document[ 0 ];
|
|
|
|
// 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 ] !== document &&
|
|
$.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) {
|
|
po.left += this.scrollParent.scrollLeft();
|
|
po.top += this.scrollParent.scrollTop();
|
|
}
|
|
|
|
if ( this._isRootNode( this.offsetParent[ 0 ] ) ) {
|
|
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" ) {
|
|
return { top: 0, left: 0 };
|
|
}
|
|
|
|
var p = this.element.position(),
|
|
scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] );
|
|
|
|
return {
|
|
top: p.top - ( parseInt( this.helper.css( "top" ), 10 ) || 0 ) +
|
|
( !scrollIsRootNode ? this.scrollParent.scrollTop() : 0 ),
|
|
left: p.left - ( parseInt( this.helper.css( "left" ), 10 ) || 0 ) +
|
|
( !scrollIsRootNode ? this.scrollParent.scrollLeft() : 0 )
|
|
};
|
|
|
|
},
|
|
|
|
_cacheMargins: function() {
|
|
this.margins = {
|
|
left: ( parseInt( this.element.css( "marginLeft" ), 10 ) || 0 ),
|
|
top: ( parseInt( this.element.css( "marginTop" ), 10 ) || 0 ),
|
|
right: ( parseInt( this.element.css( "marginRight" ), 10 ) || 0 ),
|
|
bottom: ( parseInt( this.element.css( "marginBottom" ), 10 ) || 0 )
|
|
};
|
|
},
|
|
|
|
_cacheHelperProportions: function() {
|
|
this.helperProportions = {
|
|
width: this.helper.outerWidth(),
|
|
height: this.helper.outerHeight()
|
|
};
|
|
},
|
|
|
|
_setContainment: function() {
|
|
|
|
var isUserScrollable, c, ce,
|
|
o = this.options,
|
|
document = this.document[ 0 ];
|
|
|
|
this.relativeContainer = null;
|
|
|
|
if ( !o.containment ) {
|
|
this.containment = null;
|
|
return;
|
|
}
|
|
|
|
if ( o.containment === "window" ) {
|
|
this.containment = [
|
|
$( window ).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
|
|
$( window ).scrollTop() - this.offset.relative.top - this.offset.parent.top,
|
|
$( window ).scrollLeft() + $( window ).width() -
|
|
this.helperProportions.width - this.margins.left,
|
|
$( window ).scrollTop() +
|
|
( $( window ).height() || document.body.parentNode.scrollHeight ) -
|
|
this.helperProportions.height - this.margins.top
|
|
];
|
|
return;
|
|
}
|
|
|
|
if ( o.containment === "document" ) {
|
|
this.containment = [
|
|
0,
|
|
0,
|
|
$( document ).width() - this.helperProportions.width - this.margins.left,
|
|
( $( document ).height() || document.body.parentNode.scrollHeight ) -
|
|
this.helperProportions.height - this.margins.top
|
|
];
|
|
return;
|
|
}
|
|
|
|
if ( o.containment.constructor === Array ) {
|
|
this.containment = o.containment;
|
|
return;
|
|
}
|
|
|
|
if ( o.containment === "parent" ) {
|
|
o.containment = this.helper[ 0 ].parentNode;
|
|
}
|
|
|
|
c = $( o.containment );
|
|
ce = c[ 0 ];
|
|
|
|
if ( !ce ) {
|
|
return;
|
|
}
|
|
|
|
isUserScrollable = /(scroll|auto)/.test( c.css( "overflow" ) );
|
|
|
|
this.containment = [
|
|
( parseInt( c.css( "borderLeftWidth" ), 10 ) || 0 ) +
|
|
( parseInt( c.css( "paddingLeft" ), 10 ) || 0 ),
|
|
( parseInt( c.css( "borderTopWidth" ), 10 ) || 0 ) +
|
|
( parseInt( c.css( "paddingTop" ), 10 ) || 0 ),
|
|
( isUserScrollable ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) -
|
|
( parseInt( c.css( "borderRightWidth" ), 10 ) || 0 ) -
|
|
( parseInt( c.css( "paddingRight" ), 10 ) || 0 ) -
|
|
this.helperProportions.width -
|
|
this.margins.left -
|
|
this.margins.right,
|
|
( isUserScrollable ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) -
|
|
( parseInt( c.css( "borderBottomWidth" ), 10 ) || 0 ) -
|
|
( parseInt( c.css( "paddingBottom" ), 10 ) || 0 ) -
|
|
this.helperProportions.height -
|
|
this.margins.top -
|
|
this.margins.bottom
|
|
];
|
|
this.relativeContainer = c;
|
|
},
|
|
|
|
_convertPositionTo: function( d, pos ) {
|
|
|
|
if ( !pos ) {
|
|
pos = this.position;
|
|
}
|
|
|
|
var mod = d === "absolute" ? 1 : -1,
|
|
scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] );
|
|
|
|
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.offset.scroll.top :
|
|
( scrollIsRootNode ? 0 : this.offset.scroll.top ) ) * 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.offset.scroll.left :
|
|
( scrollIsRootNode ? 0 : this.offset.scroll.left ) ) * mod )
|
|
)
|
|
};
|
|
|
|
},
|
|
|
|
_generatePosition: function( event, constrainPosition ) {
|
|
|
|
var containment, co, top, left,
|
|
o = this.options,
|
|
scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] ),
|
|
pageX = event.pageX,
|
|
pageY = event.pageY;
|
|
|
|
// Cache the scroll
|
|
if ( !scrollIsRootNode || !this.offset.scroll ) {
|
|
this.offset.scroll = {
|
|
top: this.scrollParent.scrollTop(),
|
|
left: this.scrollParent.scrollLeft()
|
|
};
|
|
}
|
|
|
|
/*
|
|
* - Position constraining -
|
|
* Constrain the position to a mix of grid, containment.
|
|
*/
|
|
|
|
// If we are not dragging yet, we won't check for options
|
|
if ( constrainPosition ) {
|
|
if ( this.containment ) {
|
|
if ( this.relativeContainer ) {
|
|
co = this.relativeContainer.offset();
|
|
containment = [
|
|
this.containment[ 0 ] + co.left,
|
|
this.containment[ 1 ] + co.top,
|
|
this.containment[ 2 ] + co.left,
|
|
this.containment[ 3 ] + co.top
|
|
];
|
|
} else {
|
|
containment = this.containment;
|
|
}
|
|
|
|
if ( event.pageX - this.offset.click.left < containment[ 0 ] ) {
|
|
pageX = containment[ 0 ] + this.offset.click.left;
|
|
}
|
|
if ( event.pageY - this.offset.click.top < containment[ 1 ] ) {
|
|
pageY = containment[ 1 ] + this.offset.click.top;
|
|
}
|
|
if ( event.pageX - this.offset.click.left > containment[ 2 ] ) {
|
|
pageX = containment[ 2 ] + this.offset.click.left;
|
|
}
|
|
if ( event.pageY - this.offset.click.top > containment[ 3 ] ) {
|
|
pageY = containment[ 3 ] + this.offset.click.top;
|
|
}
|
|
}
|
|
|
|
if ( o.grid ) {
|
|
|
|
//Check for grid elements set to 0 to prevent divide by 0 error causing invalid
|
|
// argument errors in IE (see ticket #6950)
|
|
top = o.grid[ 1 ] ? this.originalPageY + Math.round( ( pageY -
|
|
this.originalPageY ) / o.grid[ 1 ] ) * o.grid[ 1 ] : this.originalPageY;
|
|
pageY = containment ? ( ( top - this.offset.click.top >= containment[ 1 ] ||
|
|
top - this.offset.click.top > containment[ 3 ] ) ?
|
|
top :
|
|
( ( top - this.offset.click.top >= containment[ 1 ] ) ?
|
|
top - o.grid[ 1 ] : top + o.grid[ 1 ] ) ) : top;
|
|
|
|
left = o.grid[ 0 ] ? this.originalPageX +
|
|
Math.round( ( pageX - this.originalPageX ) / o.grid[ 0 ] ) * o.grid[ 0 ] :
|
|
this.originalPageX;
|
|
pageX = containment ? ( ( left - this.offset.click.left >= containment[ 0 ] ||
|
|
left - this.offset.click.left > containment[ 2 ] ) ?
|
|
left :
|
|
( ( left - this.offset.click.left >= containment[ 0 ] ) ?
|
|
left - o.grid[ 0 ] : left + o.grid[ 0 ] ) ) : left;
|
|
}
|
|
|
|
if ( o.axis === "y" ) {
|
|
pageX = this.originalPageX;
|
|
}
|
|
|
|
if ( o.axis === "x" ) {
|
|
pageY = this.originalPageY;
|
|
}
|
|
}
|
|
|
|
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.offset.scroll.top :
|
|
( scrollIsRootNode ? 0 : this.offset.scroll.top ) )
|
|
),
|
|
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.offset.scroll.left :
|
|
( scrollIsRootNode ? 0 : this.offset.scroll.left ) )
|
|
)
|
|
};
|
|
|
|
},
|
|
|
|
_clear: function() {
|
|
this._removeClass( this.helper, "ui-draggable-dragging" );
|
|
if ( this.helper[ 0 ] !== this.element[ 0 ] && !this.cancelHelperRemoval ) {
|
|
this.helper.remove();
|
|
}
|
|
this.helper = null;
|
|
this.cancelHelperRemoval = false;
|
|
if ( this.destroyOnClear ) {
|
|
this.destroy();
|
|
}
|
|
},
|
|
|
|
// From now on bulk stuff - mainly helpers
|
|
|
|
_trigger: function( type, event, ui ) {
|
|
ui = ui || this._uiHash();
|
|
$.ui.plugin.call( this, type, [ event, ui, this ], true );
|
|
|
|
// Absolute position and offset (see #6884 ) have to be recalculated after plugins
|
|
if ( /^(drag|start|stop)/.test( type ) ) {
|
|
this.positionAbs = this._convertPositionTo( "absolute" );
|
|
ui.offset = this.positionAbs;
|
|
}
|
|
return $.Widget.prototype._trigger.call( this, type, event, ui );
|
|
},
|
|
|
|
plugins: {},
|
|
|
|
_uiHash: function() {
|
|
return {
|
|
helper: this.helper,
|
|
position: this.position,
|
|
originalPosition: this.originalPosition,
|
|
offset: this.positionAbs
|
|
};
|
|
}
|
|
|
|
} );
|
|
|
|
$.ui.plugin.add( "draggable", "connectToSortable", {
|
|
start: function( event, ui, draggable ) {
|
|
var uiSortable = $.extend( {}, ui, {
|
|
item: draggable.element
|
|
} );
|
|
|
|
draggable.sortables = [];
|
|
$( draggable.options.connectToSortable ).each( function() {
|
|
var sortable = $( this ).sortable( "instance" );
|
|
|
|
if ( sortable && !sortable.options.disabled ) {
|
|
draggable.sortables.push( sortable );
|
|
|
|
// RefreshPositions is called at drag start to refresh the containerCache
|
|
// which is used in drag. This ensures it's initialized and synchronized
|
|
// with any changes that might have happened on the page since initialization.
|
|
sortable.refreshPositions();
|
|
sortable._trigger( "activate", event, uiSortable );
|
|
}
|
|
} );
|
|
},
|
|
stop: function( event, ui, draggable ) {
|
|
var uiSortable = $.extend( {}, ui, {
|
|
item: draggable.element
|
|
} );
|
|
|
|
draggable.cancelHelperRemoval = false;
|
|
|
|
$.each( draggable.sortables, function() {
|
|
var sortable = this;
|
|
|
|
if ( sortable.isOver ) {
|
|
sortable.isOver = 0;
|
|
|
|
// Allow this sortable to handle removing the helper
|
|
draggable.cancelHelperRemoval = true;
|
|
sortable.cancelHelperRemoval = false;
|
|
|
|
// Use _storedCSS To restore properties in the sortable,
|
|
// as this also handles revert (#9675) since the draggable
|
|
// may have modified them in unexpected ways (#8809)
|
|
sortable._storedCSS = {
|
|
position: sortable.placeholder.css( "position" ),
|
|
top: sortable.placeholder.css( "top" ),
|
|
left: sortable.placeholder.css( "left" )
|
|
};
|
|
|
|
sortable._mouseStop( event );
|
|
|
|
// Once drag has ended, the sortable should return to using
|
|
// its original helper, not the shared helper from draggable
|
|
sortable.options.helper = sortable.options._helper;
|
|
} else {
|
|
|
|
// Prevent this Sortable from removing the helper.
|
|
// However, don't set the draggable to remove the helper
|
|
// either as another connected Sortable may yet handle the removal.
|
|
sortable.cancelHelperRemoval = true;
|
|
|
|
sortable._trigger( "deactivate", event, uiSortable );
|
|
}
|
|
} );
|
|
},
|
|
drag: function( event, ui, draggable ) {
|
|
$.each( draggable.sortables, function() {
|
|
var innermostIntersecting = false,
|
|
sortable = this;
|
|
|
|
// Copy over variables that sortable's _intersectsWith uses
|
|
sortable.positionAbs = draggable.positionAbs;
|
|
sortable.helperProportions = draggable.helperProportions;
|
|
sortable.offset.click = draggable.offset.click;
|
|
|
|
if ( sortable._intersectsWith( sortable.containerCache ) ) {
|
|
innermostIntersecting = true;
|
|
|
|
$.each( draggable.sortables, function() {
|
|
|
|
// Copy over variables that sortable's _intersectsWith uses
|
|
this.positionAbs = draggable.positionAbs;
|
|
this.helperProportions = draggable.helperProportions;
|
|
this.offset.click = draggable.offset.click;
|
|
|
|
if ( this !== sortable &&
|
|
this._intersectsWith( this.containerCache ) &&
|
|
$.contains( sortable.element[ 0 ], this.element[ 0 ] ) ) {
|
|
innermostIntersecting = false;
|
|
}
|
|
|
|
return innermostIntersecting;
|
|
} );
|
|
}
|
|
|
|
if ( innermostIntersecting ) {
|
|
|
|
// If it intersects, we use a little isOver variable and set it once,
|
|
// so that the move-in stuff gets fired only once.
|
|
if ( !sortable.isOver ) {
|
|
sortable.isOver = 1;
|
|
|
|
// Store draggable's parent in case we need to reappend to it later.
|
|
draggable._parent = ui.helper.parent();
|
|
|
|
sortable.currentItem = ui.helper
|
|
.appendTo( sortable.element )
|
|
.data( "ui-sortable-item", true );
|
|
|
|
// Store helper option to later restore it
|
|
sortable.options._helper = sortable.options.helper;
|
|
|
|
sortable.options.helper = function() {
|
|
return ui.helper[ 0 ];
|
|
};
|
|
|
|
// Fire the start events of the sortable with our passed browser event,
|
|
// and our own helper (so it doesn't create a new one)
|
|
event.target = sortable.currentItem[ 0 ];
|
|
sortable._mouseCapture( event, true );
|
|
sortable._mouseStart( event, true, true );
|
|
|
|
// Because the browser event is way off the new appended portlet,
|
|
// modify necessary variables to reflect the changes
|
|
sortable.offset.click.top = draggable.offset.click.top;
|
|
sortable.offset.click.left = draggable.offset.click.left;
|
|
sortable.offset.parent.left -= draggable.offset.parent.left -
|
|
sortable.offset.parent.left;
|
|
sortable.offset.parent.top -= draggable.offset.parent.top -
|
|
sortable.offset.parent.top;
|
|
|
|
draggable._trigger( "toSortable", event );
|
|
|
|
// Inform draggable that the helper is in a valid drop zone,
|
|
// used solely in the revert option to handle "valid/invalid".
|
|
draggable.dropped = sortable.element;
|
|
|
|
// Need to refreshPositions of all sortables in the case that
|
|
// adding to one sortable changes the location of the other sortables (#9675)
|
|
$.each( draggable.sortables, function() {
|
|
this.refreshPositions();
|
|
} );
|
|
|
|
// Hack so receive/update callbacks work (mostly)
|
|
draggable.currentItem = draggable.element;
|
|
sortable.fromOutside = draggable;
|
|
}
|
|
|
|
if ( sortable.currentItem ) {
|
|
sortable._mouseDrag( event );
|
|
|
|
// Copy the sortable's position because the draggable's can potentially reflect
|
|
// a relative position, while sortable is always absolute, which the dragged
|
|
// element has now become. (#8809)
|
|
ui.position = sortable.position;
|
|
}
|
|
} else {
|
|
|
|
// If it doesn't intersect with the sortable, and it intersected before,
|
|
// we fake the drag stop of the sortable, but make sure it doesn't remove
|
|
// the helper by using cancelHelperRemoval.
|
|
if ( sortable.isOver ) {
|
|
|
|
sortable.isOver = 0;
|
|
sortable.cancelHelperRemoval = true;
|
|
|
|
// Calling sortable's mouseStop would trigger a revert,
|
|
// so revert must be temporarily false until after mouseStop is called.
|
|
sortable.options._revert = sortable.options.revert;
|
|
sortable.options.revert = false;
|
|
|
|
sortable._trigger( "out", event, sortable._uiHash( sortable ) );
|
|
sortable._mouseStop( event, true );
|
|
|
|
// Restore sortable behaviors that were modfied
|
|
// when the draggable entered the sortable area (#9481)
|
|
sortable.options.revert = sortable.options._revert;
|
|
sortable.options.helper = sortable.options._helper;
|
|
|
|
if ( sortable.placeholder ) {
|
|
sortable.placeholder.remove();
|
|
}
|
|
|
|
// Restore and recalculate the draggable's offset considering the sortable
|
|
// may have modified them in unexpected ways. (#8809, #10669)
|
|
ui.helper.appendTo( draggable._parent );
|
|
draggable._refreshOffsets( event );
|
|
ui.position = draggable._generatePosition( event, true );
|
|
|
|
draggable._trigger( "fromSortable", event );
|
|
|
|
// Inform draggable that the helper is no longer in a valid drop zone
|
|
draggable.dropped = false;
|
|
|
|
// Need to refreshPositions of all sortables just in case removing
|
|
// from one sortable changes the location of other sortables (#9675)
|
|
$.each( draggable.sortables, function() {
|
|
this.refreshPositions();
|
|
} );
|
|
}
|
|
}
|
|
} );
|
|
}
|
|
} );
|
|
|
|
$.ui.plugin.add( "draggable", "cursor", {
|
|
start: function( event, ui, instance ) {
|
|
var t = $( "body" ),
|
|
o = instance.options;
|
|
|
|
if ( t.css( "cursor" ) ) {
|
|
o._cursor = t.css( "cursor" );
|
|
}
|
|
t.css( "cursor", o.cursor );
|
|
},
|
|
stop: function( event, ui, instance ) {
|
|
var o = instance.options;
|
|
if ( o._cursor ) {
|
|
$( "body" ).css( "cursor", o._cursor );
|
|
}
|
|
}
|
|
} );
|
|
|
|
$.ui.plugin.add( "draggable", "opacity", {
|
|
start: function( event, ui, instance ) {
|
|
var t = $( ui.helper ),
|
|
o = instance.options;
|
|
if ( t.css( "opacity" ) ) {
|
|
o._opacity = t.css( "opacity" );
|
|
}
|
|
t.css( "opacity", o.opacity );
|
|
},
|
|
stop: function( event, ui, instance ) {
|
|
var o = instance.options;
|
|
if ( o._opacity ) {
|
|
$( ui.helper ).css( "opacity", o._opacity );
|
|
}
|
|
}
|
|
} );
|
|
|
|
$.ui.plugin.add( "draggable", "scroll", {
|
|
start: function( event, ui, i ) {
|
|
if ( !i.scrollParentNotHidden ) {
|
|
i.scrollParentNotHidden = i.helper.scrollParent( false );
|
|
}
|
|
|
|
if ( i.scrollParentNotHidden[ 0 ] !== i.document[ 0 ] &&
|
|
i.scrollParentNotHidden[ 0 ].tagName !== "HTML" ) {
|
|
i.overflowOffset = i.scrollParentNotHidden.offset();
|
|
}
|
|
},
|
|
drag: function( event, ui, i ) {
|
|
|
|
var o = i.options,
|
|
scrolled = false,
|
|
scrollParent = i.scrollParentNotHidden[ 0 ],
|
|
document = i.document[ 0 ];
|
|
|
|
if ( scrollParent !== document && scrollParent.tagName !== "HTML" ) {
|
|
if ( !o.axis || o.axis !== "x" ) {
|
|
if ( ( i.overflowOffset.top + scrollParent.offsetHeight ) - event.pageY <
|
|
o.scrollSensitivity ) {
|
|
scrollParent.scrollTop = scrolled = scrollParent.scrollTop + o.scrollSpeed;
|
|
} else if ( event.pageY - i.overflowOffset.top < o.scrollSensitivity ) {
|
|
scrollParent.scrollTop = scrolled = scrollParent.scrollTop - o.scrollSpeed;
|
|
}
|
|
}
|
|
|
|
if ( !o.axis || o.axis !== "y" ) {
|
|
if ( ( i.overflowOffset.left + scrollParent.offsetWidth ) - event.pageX <
|
|
o.scrollSensitivity ) {
|
|
scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft + o.scrollSpeed;
|
|
} else if ( event.pageX - i.overflowOffset.left < o.scrollSensitivity ) {
|
|
scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft - o.scrollSpeed;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
if ( !o.axis || o.axis !== "x" ) {
|
|
if ( event.pageY - $( document ).scrollTop() < o.scrollSensitivity ) {
|
|
scrolled = $( document ).scrollTop( $( document ).scrollTop() - o.scrollSpeed );
|
|
} else if ( $( window ).height() - ( event.pageY - $( document ).scrollTop() ) <
|
|
o.scrollSensitivity ) {
|
|
scrolled = $( document ).scrollTop( $( document ).scrollTop() + o.scrollSpeed );
|
|
}
|
|
}
|
|
|
|
if ( !o.axis || o.axis !== "y" ) {
|
|
if ( event.pageX - $( document ).scrollLeft() < o.scrollSensitivity ) {
|
|
scrolled = $( document ).scrollLeft(
|
|
$( document ).scrollLeft() - o.scrollSpeed
|
|
);
|
|
} else if ( $( window ).width() - ( event.pageX - $( document ).scrollLeft() ) <
|
|
o.scrollSensitivity ) {
|
|
scrolled = $( document ).scrollLeft(
|
|
$( document ).scrollLeft() + o.scrollSpeed
|
|
);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if ( scrolled !== false && $.ui.ddmanager && !o.dropBehaviour ) {
|
|
$.ui.ddmanager.prepareOffsets( i, event );
|
|
}
|
|
|
|
}
|
|
} );
|
|
|
|
$.ui.plugin.add( "draggable", "snap", {
|
|
start: function( event, ui, i ) {
|
|
|
|
var o = i.options;
|
|
|
|
i.snapElements = [];
|
|
|
|
$( o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap )
|
|
.each( function() {
|
|
var $t = $( this ),
|
|
$o = $t.offset();
|
|
if ( this !== i.element[ 0 ] ) {
|
|
i.snapElements.push( {
|
|
item: this,
|
|
width: $t.outerWidth(), height: $t.outerHeight(),
|
|
top: $o.top, left: $o.left
|
|
} );
|
|
}
|
|
} );
|
|
|
|
},
|
|
drag: function( event, ui, inst ) {
|
|
|
|
var ts, bs, ls, rs, l, r, t, b, i, first,
|
|
o = inst.options,
|
|
d = o.snapTolerance,
|
|
x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
|
|
y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;
|
|
|
|
for ( i = inst.snapElements.length - 1; i >= 0; i-- ) {
|
|
|
|
l = inst.snapElements[ i ].left - inst.margins.left;
|
|
r = l + inst.snapElements[ i ].width;
|
|
t = inst.snapElements[ i ].top - inst.margins.top;
|
|
b = t + inst.snapElements[ i ].height;
|
|
|
|
if ( x2 < l - d || x1 > r + d || y2 < t - d || y1 > b + d ||
|
|
!$.contains( inst.snapElements[ i ].item.ownerDocument,
|
|
inst.snapElements[ i ].item ) ) {
|
|
if ( inst.snapElements[ i ].snapping ) {
|
|
( inst.options.snap.release &&
|
|
inst.options.snap.release.call(
|
|
inst.element,
|
|
event,
|
|
$.extend( inst._uiHash(), { snapItem: inst.snapElements[ i ].item } )
|
|
) );
|
|
}
|
|
inst.snapElements[ i ].snapping = false;
|
|
continue;
|
|
}
|
|
|
|
if ( o.snapMode !== "inner" ) {
|
|
ts = Math.abs( t - y2 ) <= d;
|
|
bs = Math.abs( b - y1 ) <= d;
|
|
ls = Math.abs( l - x2 ) <= d;
|
|
rs = Math.abs( r - x1 ) <= d;
|
|
if ( ts ) {
|
|
ui.position.top = inst._convertPositionTo( "relative", {
|
|
top: t - inst.helperProportions.height,
|
|
left: 0
|
|
} ).top;
|
|
}
|
|
if ( bs ) {
|
|
ui.position.top = inst._convertPositionTo( "relative", {
|
|
top: b,
|
|
left: 0
|
|
} ).top;
|
|
}
|
|
if ( ls ) {
|
|
ui.position.left = inst._convertPositionTo( "relative", {
|
|
top: 0,
|
|
left: l - inst.helperProportions.width
|
|
} ).left;
|
|
}
|
|
if ( rs ) {
|
|
ui.position.left = inst._convertPositionTo( "relative", {
|
|
top: 0,
|
|
left: r
|
|
} ).left;
|
|
}
|
|
}
|
|
|
|
first = ( ts || bs || ls || rs );
|
|
|
|
if ( o.snapMode !== "outer" ) {
|
|
ts = Math.abs( t - y1 ) <= d;
|
|
bs = Math.abs( b - y2 ) <= d;
|
|
ls = Math.abs( l - x1 ) <= d;
|
|
rs = Math.abs( r - x2 ) <= d;
|
|
if ( ts ) {
|
|
ui.position.top = inst._convertPositionTo( "relative", {
|
|
top: t,
|
|
left: 0
|
|
} ).top;
|
|
}
|
|
if ( bs ) {
|
|
ui.position.top = inst._convertPositionTo( "relative", {
|
|
top: b - inst.helperProportions.height,
|
|
left: 0
|
|
} ).top;
|
|
}
|
|
if ( ls ) {
|
|
ui.position.left = inst._convertPositionTo( "relative", {
|
|
top: 0,
|
|
left: l
|
|
} ).left;
|
|
}
|
|
if ( rs ) {
|
|
ui.position.left = inst._convertPositionTo( "relative", {
|
|
top: 0,
|
|
left: r - inst.helperProportions.width
|
|
} ).left;
|
|
}
|
|
}
|
|
|
|
if ( !inst.snapElements[ i ].snapping && ( ts || bs || ls || rs || first ) ) {
|
|
( inst.options.snap.snap &&
|
|
inst.options.snap.snap.call(
|
|
inst.element,
|
|
event,
|
|
$.extend( inst._uiHash(), {
|
|
snapItem: inst.snapElements[ i ].item
|
|
} ) ) );
|
|
}
|
|
inst.snapElements[ i ].snapping = ( ts || bs || ls || rs || first );
|
|
|
|
}
|
|
|
|
}
|
|
} );
|
|
|
|
$.ui.plugin.add( "draggable", "stack", {
|
|
start: function( event, ui, instance ) {
|
|
var min,
|
|
o = instance.options,
|
|
group = $.makeArray( $( o.stack ) ).sort( function( a, b ) {
|
|
return ( parseInt( $( a ).css( "zIndex" ), 10 ) || 0 ) -
|
|
( parseInt( $( b ).css( "zIndex" ), 10 ) || 0 );
|
|
} );
|
|
|
|
if ( !group.length ) { return; }
|
|
|
|
min = parseInt( $( group[ 0 ] ).css( "zIndex" ), 10 ) || 0;
|
|
$( group ).each( function( i ) {
|
|
$( this ).css( "zIndex", min + i );
|
|
} );
|
|
this.css( "zIndex", ( min + group.length ) );
|
|
}
|
|
} );
|
|
|
|
$.ui.plugin.add( "draggable", "zIndex", {
|
|
start: function( event, ui, instance ) {
|
|
var t = $( ui.helper ),
|
|
o = instance.options;
|
|
|
|
if ( t.css( "zIndex" ) ) {
|
|
o._zIndex = t.css( "zIndex" );
|
|
}
|
|
t.css( "zIndex", o.zIndex );
|
|
},
|
|
stop: function( event, ui, instance ) {
|
|
var o = instance.options;
|
|
|
|
if ( o._zIndex ) {
|
|
$( ui.helper ).css( "zIndex", o._zIndex );
|
|
}
|
|
}
|
|
} );
|
|
|
|
return $.ui.draggable;
|
|
|
|
} ) );
|