Brak opisu

draggable.js 35KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245
  1. /*!
  2. * jQuery UI Draggable 1.12.1
  3. * http://jqueryui.com
  4. *
  5. * Copyright jQuery Foundation and other contributors
  6. * Released under the MIT license.
  7. * http://jquery.org/license
  8. */
  9. //>>label: Draggable
  10. //>>group: Interactions
  11. //>>description: Enables dragging functionality for any element.
  12. //>>docs: http://api.jqueryui.com/draggable/
  13. //>>demos: http://jqueryui.com/draggable/
  14. //>>css.structure: ../../themes/base/draggable.css
  15. ( function( factory ) {
  16. if ( typeof define === "function" && define.amd ) {
  17. // AMD. Register as an anonymous module.
  18. define( [
  19. "jquery",
  20. "./mouse",
  21. "./core"
  22. ], factory );
  23. } else {
  24. // Browser globals
  25. factory( jQuery );
  26. }
  27. }( function( $ ) {
  28. $.widget( "ui.draggable", $.ui.mouse, {
  29. version: "1.12.1",
  30. widgetEventPrefix: "drag",
  31. options: {
  32. addClasses: true,
  33. appendTo: "parent",
  34. axis: false,
  35. connectToSortable: false,
  36. containment: false,
  37. cursor: "auto",
  38. cursorAt: false,
  39. grid: false,
  40. handle: false,
  41. helper: "original",
  42. iframeFix: false,
  43. opacity: false,
  44. refreshPositions: false,
  45. revert: false,
  46. revertDuration: 500,
  47. scope: "default",
  48. scroll: true,
  49. scrollSensitivity: 20,
  50. scrollSpeed: 20,
  51. snap: false,
  52. snapMode: "both",
  53. snapTolerance: 20,
  54. stack: false,
  55. zIndex: false,
  56. // Callbacks
  57. drag: null,
  58. start: null,
  59. stop: null
  60. },
  61. _create: function() {
  62. if ( this.options.helper === "original" ) {
  63. this._setPositionRelative();
  64. }
  65. if ( this.options.addClasses ) {
  66. this._addClass( "ui-draggable" );
  67. }
  68. this._setHandleClassName();
  69. this._mouseInit();
  70. },
  71. _setOption: function( key, value ) {
  72. this._super( key, value );
  73. if ( key === "handle" ) {
  74. this._removeHandleClassName();
  75. this._setHandleClassName();
  76. }
  77. },
  78. _destroy: function() {
  79. if ( ( this.helper || this.element ).is( ".ui-draggable-dragging" ) ) {
  80. this.destroyOnClear = true;
  81. return;
  82. }
  83. this._removeHandleClassName();
  84. this._mouseDestroy();
  85. },
  86. _mouseCapture: function( event ) {
  87. var o = this.options;
  88. // Among others, prevent a drag on a resizable-handle
  89. if ( this.helper || o.disabled ||
  90. $( event.target ).closest( ".ui-resizable-handle" ).length > 0 ) {
  91. return false;
  92. }
  93. //Quit if we're not on a valid handle
  94. this.handle = this._getHandle( event );
  95. if ( !this.handle ) {
  96. return false;
  97. }
  98. this._blurActiveElement( event );
  99. this._blockFrames( o.iframeFix === true ? "iframe" : o.iframeFix );
  100. return true;
  101. },
  102. _blockFrames: function( selector ) {
  103. this.iframeBlocks = this.document.find( selector ).map( function() {
  104. var iframe = $( this );
  105. return $( "<div>" )
  106. .css( "position", "absolute" )
  107. .appendTo( iframe.parent() )
  108. .outerWidth( iframe.outerWidth() )
  109. .outerHeight( iframe.outerHeight() )
  110. .offset( iframe.offset() )[ 0 ];
  111. } );
  112. },
  113. _unblockFrames: function() {
  114. if ( this.iframeBlocks ) {
  115. this.iframeBlocks.remove();
  116. delete this.iframeBlocks;
  117. }
  118. },
  119. _blurActiveElement: function( event ) {
  120. var activeElement = $.ui.safeActiveElement( this.document[ 0 ] ),
  121. target = $( event.target );
  122. // Don't blur if the event occurred on an element that is within
  123. // the currently focused element
  124. // See #10527, #12472
  125. if ( target.closest( activeElement ).length ) {
  126. return;
  127. }
  128. // Blur any element that currently has focus, see #4261
  129. $.ui.safeBlur( activeElement );
  130. },
  131. _mouseStart: function( event ) {
  132. var o = this.options;
  133. //Create and append the visible helper
  134. this.helper = this._createHelper( event );
  135. this._addClass( this.helper, "ui-draggable-dragging" );
  136. //Cache the helper size
  137. this._cacheHelperProportions();
  138. //If ddmanager is used for droppables, set the global draggable
  139. if ( $.ui.ddmanager ) {
  140. $.ui.ddmanager.current = this;
  141. }
  142. /*
  143. * - Position generation -
  144. * This block generates everything position related - it's the core of draggables.
  145. */
  146. //Cache the margins of the original element
  147. this._cacheMargins();
  148. //Store the helper's css position
  149. this.cssPosition = this.helper.css( "position" );
  150. this.scrollParent = this.helper.scrollParent( true );
  151. this.offsetParent = this.helper.offsetParent();
  152. this.hasFixedAncestor = this.helper.parents().filter( function() {
  153. return $( this ).css( "position" ) === "fixed";
  154. } ).length > 0;
  155. //The element's absolute position on the page minus margins
  156. this.positionAbs = this.element.offset();
  157. this._refreshOffsets( event );
  158. //Generate the original position
  159. this.originalPosition = this.position = this._generatePosition( event, false );
  160. this.originalPageX = event.pageX;
  161. this.originalPageY = event.pageY;
  162. //Adjust the mouse offset relative to the helper if "cursorAt" is supplied
  163. ( o.cursorAt && this._adjustOffsetFromHelper( o.cursorAt ) );
  164. //Set a containment if given in the options
  165. this._setContainment();
  166. //Trigger event + callbacks
  167. if ( this._trigger( "start", event ) === false ) {
  168. this._clear();
  169. return false;
  170. }
  171. //Recache the helper size
  172. this._cacheHelperProportions();
  173. //Prepare the droppable offsets
  174. if ( $.ui.ddmanager && !o.dropBehaviour ) {
  175. $.ui.ddmanager.prepareOffsets( this, event );
  176. }
  177. // Execute the drag once - this causes the helper not to be visible before getting its
  178. // correct position
  179. this._mouseDrag( event, true );
  180. // If the ddmanager is used for droppables, inform the manager that dragging has started
  181. // (see #5003)
  182. if ( $.ui.ddmanager ) {
  183. $.ui.ddmanager.dragStart( this, event );
  184. }
  185. return true;
  186. },
  187. _refreshOffsets: function( event ) {
  188. this.offset = {
  189. top: this.positionAbs.top - this.margins.top,
  190. left: this.positionAbs.left - this.margins.left,
  191. scroll: false,
  192. parent: this._getParentOffset(),
  193. relative: this._getRelativeOffset()
  194. };
  195. this.offset.click = {
  196. left: event.pageX - this.offset.left,
  197. top: event.pageY - this.offset.top
  198. };
  199. },
  200. _mouseDrag: function( event, noPropagation ) {
  201. // reset any necessary cached properties (see #5009)
  202. if ( this.hasFixedAncestor ) {
  203. this.offset.parent = this._getParentOffset();
  204. }
  205. //Compute the helpers position
  206. this.position = this._generatePosition( event, true );
  207. this.positionAbs = this._convertPositionTo( "absolute" );
  208. //Call plugins and callbacks and use the resulting position if something is returned
  209. if ( !noPropagation ) {
  210. var ui = this._uiHash();
  211. if ( this._trigger( "drag", event, ui ) === false ) {
  212. this._mouseUp( new $.Event( "mouseup", event ) );
  213. return false;
  214. }
  215. this.position = ui.position;
  216. }
  217. this.helper[ 0 ].style.left = this.position.left + "px";
  218. this.helper[ 0 ].style.top = this.position.top + "px";
  219. if ( $.ui.ddmanager ) {
  220. $.ui.ddmanager.drag( this, event );
  221. }
  222. return false;
  223. },
  224. _mouseStop: function( event ) {
  225. //If we are using droppables, inform the manager about the drop
  226. var that = this,
  227. dropped = false;
  228. if ( $.ui.ddmanager && !this.options.dropBehaviour ) {
  229. dropped = $.ui.ddmanager.drop( this, event );
  230. }
  231. //if a drop comes from outside (a sortable)
  232. if ( this.dropped ) {
  233. dropped = this.dropped;
  234. this.dropped = false;
  235. }
  236. if ( ( this.options.revert === "invalid" && !dropped ) ||
  237. ( this.options.revert === "valid" && dropped ) ||
  238. this.options.revert === true || ( $.isFunction( this.options.revert ) &&
  239. this.options.revert.call( this.element, dropped ) )
  240. ) {
  241. $( this.helper ).animate(
  242. this.originalPosition,
  243. parseInt( this.options.revertDuration, 10 ),
  244. function() {
  245. if ( that._trigger( "stop", event ) !== false ) {
  246. that._clear();
  247. }
  248. }
  249. );
  250. } else {
  251. if ( this._trigger( "stop", event ) !== false ) {
  252. this._clear();
  253. }
  254. }
  255. return false;
  256. },
  257. _mouseUp: function( event ) {
  258. this._unblockFrames();
  259. // If the ddmanager is used for droppables, inform the manager that dragging has stopped
  260. // (see #5003)
  261. if ( $.ui.ddmanager ) {
  262. $.ui.ddmanager.dragStop( this, event );
  263. }
  264. // Only need to focus if the event occurred on the draggable itself, see #10527
  265. if ( this.handleElement.is( event.target ) ) {
  266. // The interaction is over; whether or not the click resulted in a drag,
  267. // focus the element
  268. this.element.trigger( "focus" );
  269. }
  270. return $.ui.mouse.prototype._mouseUp.call( this, event );
  271. },
  272. cancel: function() {
  273. if ( this.helper.is( ".ui-draggable-dragging" ) ) {
  274. this._mouseUp( new $.Event( "mouseup", { target: this.element[ 0 ] } ) );
  275. } else {
  276. this._clear();
  277. }
  278. return this;
  279. },
  280. _getHandle: function( event ) {
  281. return this.options.handle ?
  282. !!$( event.target ).closest( this.element.find( this.options.handle ) ).length :
  283. true;
  284. },
  285. _setHandleClassName: function() {
  286. this.handleElement = this.options.handle ?
  287. this.element.find( this.options.handle ) : this.element;
  288. this._addClass( this.handleElement, "ui-draggable-handle" );
  289. },
  290. _removeHandleClassName: function() {
  291. this._removeClass( this.handleElement, "ui-draggable-handle" );
  292. },
  293. _createHelper: function( event ) {
  294. var o = this.options,
  295. helperIsFunction = $.isFunction( o.helper ),
  296. helper = helperIsFunction ?
  297. $( o.helper.apply( this.element[ 0 ], [ event ] ) ) :
  298. ( o.helper === "clone" ?
  299. this.element.clone().removeAttr( "id" ) :
  300. this.element );
  301. if ( !helper.parents( "body" ).length ) {
  302. helper.appendTo( ( o.appendTo === "parent" ?
  303. this.element[ 0 ].parentNode :
  304. o.appendTo ) );
  305. }
  306. // Http://bugs.jqueryui.com/ticket/9446
  307. // a helper function can return the original element
  308. // which wouldn't have been set to relative in _create
  309. if ( helperIsFunction && helper[ 0 ] === this.element[ 0 ] ) {
  310. this._setPositionRelative();
  311. }
  312. if ( helper[ 0 ] !== this.element[ 0 ] &&
  313. !( /(fixed|absolute)/ ).test( helper.css( "position" ) ) ) {
  314. helper.css( "position", "absolute" );
  315. }
  316. return helper;
  317. },
  318. _setPositionRelative: function() {
  319. if ( !( /^(?:r|a|f)/ ).test( this.element.css( "position" ) ) ) {
  320. this.element[ 0 ].style.position = "relative";
  321. }
  322. },
  323. _adjustOffsetFromHelper: function( obj ) {
  324. if ( typeof obj === "string" ) {
  325. obj = obj.split( " " );
  326. }
  327. if ( $.isArray( obj ) ) {
  328. obj = { left: +obj[ 0 ], top: +obj[ 1 ] || 0 };
  329. }
  330. if ( "left" in obj ) {
  331. this.offset.click.left = obj.left + this.margins.left;
  332. }
  333. if ( "right" in obj ) {
  334. this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
  335. }
  336. if ( "top" in obj ) {
  337. this.offset.click.top = obj.top + this.margins.top;
  338. }
  339. if ( "bottom" in obj ) {
  340. this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
  341. }
  342. },
  343. _isRootNode: function( element ) {
  344. return ( /(html|body)/i ).test( element.tagName ) || element === this.document[ 0 ];
  345. },
  346. _getParentOffset: function() {
  347. //Get the offsetParent and cache its position
  348. var po = this.offsetParent.offset(),
  349. document = this.document[ 0 ];
  350. // This is a special case where we need to modify a offset calculated on start, since the
  351. // following happened:
  352. // 1. The position of the helper is absolute, so it's position is calculated based on the
  353. // next positioned parent
  354. // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't
  355. // the document, which means that the scroll is included in the initial calculation of the
  356. // offset of the parent, and never recalculated upon drag
  357. if ( this.cssPosition === "absolute" && this.scrollParent[ 0 ] !== document &&
  358. $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) {
  359. po.left += this.scrollParent.scrollLeft();
  360. po.top += this.scrollParent.scrollTop();
  361. }
  362. if ( this._isRootNode( this.offsetParent[ 0 ] ) ) {
  363. po = { top: 0, left: 0 };
  364. }
  365. return {
  366. top: po.top + ( parseInt( this.offsetParent.css( "borderTopWidth" ), 10 ) || 0 ),
  367. left: po.left + ( parseInt( this.offsetParent.css( "borderLeftWidth" ), 10 ) || 0 )
  368. };
  369. },
  370. _getRelativeOffset: function() {
  371. if ( this.cssPosition !== "relative" ) {
  372. return { top: 0, left: 0 };
  373. }
  374. var p = this.element.position(),
  375. scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] );
  376. return {
  377. top: p.top - ( parseInt( this.helper.css( "top" ), 10 ) || 0 ) +
  378. ( !scrollIsRootNode ? this.scrollParent.scrollTop() : 0 ),
  379. left: p.left - ( parseInt( this.helper.css( "left" ), 10 ) || 0 ) +
  380. ( !scrollIsRootNode ? this.scrollParent.scrollLeft() : 0 )
  381. };
  382. },
  383. _cacheMargins: function() {
  384. this.margins = {
  385. left: ( parseInt( this.element.css( "marginLeft" ), 10 ) || 0 ),
  386. top: ( parseInt( this.element.css( "marginTop" ), 10 ) || 0 ),
  387. right: ( parseInt( this.element.css( "marginRight" ), 10 ) || 0 ),
  388. bottom: ( parseInt( this.element.css( "marginBottom" ), 10 ) || 0 )
  389. };
  390. },
  391. _cacheHelperProportions: function() {
  392. this.helperProportions = {
  393. width: this.helper.outerWidth(),
  394. height: this.helper.outerHeight()
  395. };
  396. },
  397. _setContainment: function() {
  398. var isUserScrollable, c, ce,
  399. o = this.options,
  400. document = this.document[ 0 ];
  401. this.relativeContainer = null;
  402. if ( !o.containment ) {
  403. this.containment = null;
  404. return;
  405. }
  406. if ( o.containment === "window" ) {
  407. this.containment = [
  408. $( window ).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
  409. $( window ).scrollTop() - this.offset.relative.top - this.offset.parent.top,
  410. $( window ).scrollLeft() + $( window ).width() -
  411. this.helperProportions.width - this.margins.left,
  412. $( window ).scrollTop() +
  413. ( $( window ).height() || document.body.parentNode.scrollHeight ) -
  414. this.helperProportions.height - this.margins.top
  415. ];
  416. return;
  417. }
  418. if ( o.containment === "document" ) {
  419. this.containment = [
  420. 0,
  421. 0,
  422. $( document ).width() - this.helperProportions.width - this.margins.left,
  423. ( $( document ).height() || document.body.parentNode.scrollHeight ) -
  424. this.helperProportions.height - this.margins.top
  425. ];
  426. return;
  427. }
  428. if ( o.containment.constructor === Array ) {
  429. this.containment = o.containment;
  430. return;
  431. }
  432. if ( o.containment === "parent" ) {
  433. o.containment = this.helper[ 0 ].parentNode;
  434. }
  435. c = $( o.containment );
  436. ce = c[ 0 ];
  437. if ( !ce ) {
  438. return;
  439. }
  440. isUserScrollable = /(scroll|auto)/.test( c.css( "overflow" ) );
  441. this.containment = [
  442. ( parseInt( c.css( "borderLeftWidth" ), 10 ) || 0 ) +
  443. ( parseInt( c.css( "paddingLeft" ), 10 ) || 0 ),
  444. ( parseInt( c.css( "borderTopWidth" ), 10 ) || 0 ) +
  445. ( parseInt( c.css( "paddingTop" ), 10 ) || 0 ),
  446. ( isUserScrollable ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) -
  447. ( parseInt( c.css( "borderRightWidth" ), 10 ) || 0 ) -
  448. ( parseInt( c.css( "paddingRight" ), 10 ) || 0 ) -
  449. this.helperProportions.width -
  450. this.margins.left -
  451. this.margins.right,
  452. ( isUserScrollable ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) -
  453. ( parseInt( c.css( "borderBottomWidth" ), 10 ) || 0 ) -
  454. ( parseInt( c.css( "paddingBottom" ), 10 ) || 0 ) -
  455. this.helperProportions.height -
  456. this.margins.top -
  457. this.margins.bottom
  458. ];
  459. this.relativeContainer = c;
  460. },
  461. _convertPositionTo: function( d, pos ) {
  462. if ( !pos ) {
  463. pos = this.position;
  464. }
  465. var mod = d === "absolute" ? 1 : -1,
  466. scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] );
  467. return {
  468. top: (
  469. // The absolute mouse position
  470. pos.top +
  471. // Only for relative positioned nodes: Relative offset from element to offset parent
  472. this.offset.relative.top * mod +
  473. // The offsetParent's offset without borders (offset + border)
  474. this.offset.parent.top * mod -
  475. ( ( this.cssPosition === "fixed" ?
  476. -this.offset.scroll.top :
  477. ( scrollIsRootNode ? 0 : this.offset.scroll.top ) ) * mod )
  478. ),
  479. left: (
  480. // The absolute mouse position
  481. pos.left +
  482. // Only for relative positioned nodes: Relative offset from element to offset parent
  483. this.offset.relative.left * mod +
  484. // The offsetParent's offset without borders (offset + border)
  485. this.offset.parent.left * mod -
  486. ( ( this.cssPosition === "fixed" ?
  487. -this.offset.scroll.left :
  488. ( scrollIsRootNode ? 0 : this.offset.scroll.left ) ) * mod )
  489. )
  490. };
  491. },
  492. _generatePosition: function( event, constrainPosition ) {
  493. var containment, co, top, left,
  494. o = this.options,
  495. scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] ),
  496. pageX = event.pageX,
  497. pageY = event.pageY;
  498. // Cache the scroll
  499. if ( !scrollIsRootNode || !this.offset.scroll ) {
  500. this.offset.scroll = {
  501. top: this.scrollParent.scrollTop(),
  502. left: this.scrollParent.scrollLeft()
  503. };
  504. }
  505. /*
  506. * - Position constraining -
  507. * Constrain the position to a mix of grid, containment.
  508. */
  509. // If we are not dragging yet, we won't check for options
  510. if ( constrainPosition ) {
  511. if ( this.containment ) {
  512. if ( this.relativeContainer ) {
  513. co = this.relativeContainer.offset();
  514. containment = [
  515. this.containment[ 0 ] + co.left,
  516. this.containment[ 1 ] + co.top,
  517. this.containment[ 2 ] + co.left,
  518. this.containment[ 3 ] + co.top
  519. ];
  520. } else {
  521. containment = this.containment;
  522. }
  523. if ( event.pageX - this.offset.click.left < containment[ 0 ] ) {
  524. pageX = containment[ 0 ] + this.offset.click.left;
  525. }
  526. if ( event.pageY - this.offset.click.top < containment[ 1 ] ) {
  527. pageY = containment[ 1 ] + this.offset.click.top;
  528. }
  529. if ( event.pageX - this.offset.click.left > containment[ 2 ] ) {
  530. pageX = containment[ 2 ] + this.offset.click.left;
  531. }
  532. if ( event.pageY - this.offset.click.top > containment[ 3 ] ) {
  533. pageY = containment[ 3 ] + this.offset.click.top;
  534. }
  535. }
  536. if ( o.grid ) {
  537. //Check for grid elements set to 0 to prevent divide by 0 error causing invalid
  538. // argument errors in IE (see ticket #6950)
  539. top = o.grid[ 1 ] ? this.originalPageY + Math.round( ( pageY -
  540. this.originalPageY ) / o.grid[ 1 ] ) * o.grid[ 1 ] : this.originalPageY;
  541. pageY = containment ? ( ( top - this.offset.click.top >= containment[ 1 ] ||
  542. top - this.offset.click.top > containment[ 3 ] ) ?
  543. top :
  544. ( ( top - this.offset.click.top >= containment[ 1 ] ) ?
  545. top - o.grid[ 1 ] : top + o.grid[ 1 ] ) ) : top;
  546. left = o.grid[ 0 ] ? this.originalPageX +
  547. Math.round( ( pageX - this.originalPageX ) / o.grid[ 0 ] ) * o.grid[ 0 ] :
  548. this.originalPageX;
  549. pageX = containment ? ( ( left - this.offset.click.left >= containment[ 0 ] ||
  550. left - this.offset.click.left > containment[ 2 ] ) ?
  551. left :
  552. ( ( left - this.offset.click.left >= containment[ 0 ] ) ?
  553. left - o.grid[ 0 ] : left + o.grid[ 0 ] ) ) : left;
  554. }
  555. if ( o.axis === "y" ) {
  556. pageX = this.originalPageX;
  557. }
  558. if ( o.axis === "x" ) {
  559. pageY = this.originalPageY;
  560. }
  561. }
  562. return {
  563. top: (
  564. // The absolute mouse position
  565. pageY -
  566. // Click offset (relative to the element)
  567. this.offset.click.top -
  568. // Only for relative positioned nodes: Relative offset from element to offset parent
  569. this.offset.relative.top -
  570. // The offsetParent's offset without borders (offset + border)
  571. this.offset.parent.top +
  572. ( this.cssPosition === "fixed" ?
  573. -this.offset.scroll.top :
  574. ( scrollIsRootNode ? 0 : this.offset.scroll.top ) )
  575. ),
  576. left: (
  577. // The absolute mouse position
  578. pageX -
  579. // Click offset (relative to the element)
  580. this.offset.click.left -
  581. // Only for relative positioned nodes: Relative offset from element to offset parent
  582. this.offset.relative.left -
  583. // The offsetParent's offset without borders (offset + border)
  584. this.offset.parent.left +
  585. ( this.cssPosition === "fixed" ?
  586. -this.offset.scroll.left :
  587. ( scrollIsRootNode ? 0 : this.offset.scroll.left ) )
  588. )
  589. };
  590. },
  591. _clear: function() {
  592. this._removeClass( this.helper, "ui-draggable-dragging" );
  593. if ( this.helper[ 0 ] !== this.element[ 0 ] && !this.cancelHelperRemoval ) {
  594. this.helper.remove();
  595. }
  596. this.helper = null;
  597. this.cancelHelperRemoval = false;
  598. if ( this.destroyOnClear ) {
  599. this.destroy();
  600. }
  601. },
  602. // From now on bulk stuff - mainly helpers
  603. _trigger: function( type, event, ui ) {
  604. ui = ui || this._uiHash();
  605. $.ui.plugin.call( this, type, [ event, ui, this ], true );
  606. // Absolute position and offset (see #6884 ) have to be recalculated after plugins
  607. if ( /^(drag|start|stop)/.test( type ) ) {
  608. this.positionAbs = this._convertPositionTo( "absolute" );
  609. ui.offset = this.positionAbs;
  610. }
  611. return $.Widget.prototype._trigger.call( this, type, event, ui );
  612. },
  613. plugins: {},
  614. _uiHash: function() {
  615. return {
  616. helper: this.helper,
  617. position: this.position,
  618. originalPosition: this.originalPosition,
  619. offset: this.positionAbs
  620. };
  621. }
  622. } );
  623. $.ui.plugin.add( "draggable", "connectToSortable", {
  624. start: function( event, ui, draggable ) {
  625. var uiSortable = $.extend( {}, ui, {
  626. item: draggable.element
  627. } );
  628. draggable.sortables = [];
  629. $( draggable.options.connectToSortable ).each( function() {
  630. var sortable = $( this ).sortable( "instance" );
  631. if ( sortable && !sortable.options.disabled ) {
  632. draggable.sortables.push( sortable );
  633. // RefreshPositions is called at drag start to refresh the containerCache
  634. // which is used in drag. This ensures it's initialized and synchronized
  635. // with any changes that might have happened on the page since initialization.
  636. sortable.refreshPositions();
  637. sortable._trigger( "activate", event, uiSortable );
  638. }
  639. } );
  640. },
  641. stop: function( event, ui, draggable ) {
  642. var uiSortable = $.extend( {}, ui, {
  643. item: draggable.element
  644. } );
  645. draggable.cancelHelperRemoval = false;
  646. $.each( draggable.sortables, function() {
  647. var sortable = this;
  648. if ( sortable.isOver ) {
  649. sortable.isOver = 0;
  650. // Allow this sortable to handle removing the helper
  651. draggable.cancelHelperRemoval = true;
  652. sortable.cancelHelperRemoval = false;
  653. // Use _storedCSS To restore properties in the sortable,
  654. // as this also handles revert (#9675) since the draggable
  655. // may have modified them in unexpected ways (#8809)
  656. sortable._storedCSS = {
  657. position: sortable.placeholder.css( "position" ),
  658. top: sortable.placeholder.css( "top" ),
  659. left: sortable.placeholder.css( "left" )
  660. };
  661. sortable._mouseStop( event );
  662. // Once drag has ended, the sortable should return to using
  663. // its original helper, not the shared helper from draggable
  664. sortable.options.helper = sortable.options._helper;
  665. } else {
  666. // Prevent this Sortable from removing the helper.
  667. // However, don't set the draggable to remove the helper
  668. // either as another connected Sortable may yet handle the removal.
  669. sortable.cancelHelperRemoval = true;
  670. sortable._trigger( "deactivate", event, uiSortable );
  671. }
  672. } );
  673. },
  674. drag: function( event, ui, draggable ) {
  675. $.each( draggable.sortables, function() {
  676. var innermostIntersecting = false,
  677. sortable = this;
  678. // Copy over variables that sortable's _intersectsWith uses
  679. sortable.positionAbs = draggable.positionAbs;
  680. sortable.helperProportions = draggable.helperProportions;
  681. sortable.offset.click = draggable.offset.click;
  682. if ( sortable._intersectsWith( sortable.containerCache ) ) {
  683. innermostIntersecting = true;
  684. $.each( draggable.sortables, function() {
  685. // Copy over variables that sortable's _intersectsWith uses
  686. this.positionAbs = draggable.positionAbs;
  687. this.helperProportions = draggable.helperProportions;
  688. this.offset.click = draggable.offset.click;
  689. if ( this !== sortable &&
  690. this._intersectsWith( this.containerCache ) &&
  691. $.contains( sortable.element[ 0 ], this.element[ 0 ] ) ) {
  692. innermostIntersecting = false;
  693. }
  694. return innermostIntersecting;
  695. } );
  696. }
  697. if ( innermostIntersecting ) {
  698. // If it intersects, we use a little isOver variable and set it once,
  699. // so that the move-in stuff gets fired only once.
  700. if ( !sortable.isOver ) {
  701. sortable.isOver = 1;
  702. // Store draggable's parent in case we need to reappend to it later.
  703. draggable._parent = ui.helper.parent();
  704. sortable.currentItem = ui.helper
  705. .appendTo( sortable.element )
  706. .data( "ui-sortable-item", true );
  707. // Store helper option to later restore it
  708. sortable.options._helper = sortable.options.helper;
  709. sortable.options.helper = function() {
  710. return ui.helper[ 0 ];
  711. };
  712. // Fire the start events of the sortable with our passed browser event,
  713. // and our own helper (so it doesn't create a new one)
  714. event.target = sortable.currentItem[ 0 ];
  715. sortable._mouseCapture( event, true );
  716. sortable._mouseStart( event, true, true );
  717. // Because the browser event is way off the new appended portlet,
  718. // modify necessary variables to reflect the changes
  719. sortable.offset.click.top = draggable.offset.click.top;
  720. sortable.offset.click.left = draggable.offset.click.left;
  721. sortable.offset.parent.left -= draggable.offset.parent.left -
  722. sortable.offset.parent.left;
  723. sortable.offset.parent.top -= draggable.offset.parent.top -
  724. sortable.offset.parent.top;
  725. draggable._trigger( "toSortable", event );
  726. // Inform draggable that the helper is in a valid drop zone,
  727. // used solely in the revert option to handle "valid/invalid".
  728. draggable.dropped = sortable.element;
  729. // Need to refreshPositions of all sortables in the case that
  730. // adding to one sortable changes the location of the other sortables (#9675)
  731. $.each( draggable.sortables, function() {
  732. this.refreshPositions();
  733. } );
  734. // Hack so receive/update callbacks work (mostly)
  735. draggable.currentItem = draggable.element;
  736. sortable.fromOutside = draggable;
  737. }
  738. if ( sortable.currentItem ) {
  739. sortable._mouseDrag( event );
  740. // Copy the sortable's position because the draggable's can potentially reflect
  741. // a relative position, while sortable is always absolute, which the dragged
  742. // element has now become. (#8809)
  743. ui.position = sortable.position;
  744. }
  745. } else {
  746. // If it doesn't intersect with the sortable, and it intersected before,
  747. // we fake the drag stop of the sortable, but make sure it doesn't remove
  748. // the helper by using cancelHelperRemoval.
  749. if ( sortable.isOver ) {
  750. sortable.isOver = 0;
  751. sortable.cancelHelperRemoval = true;
  752. // Calling sortable's mouseStop would trigger a revert,
  753. // so revert must be temporarily false until after mouseStop is called.
  754. sortable.options._revert = sortable.options.revert;
  755. sortable.options.revert = false;
  756. sortable._trigger( "out", event, sortable._uiHash( sortable ) );
  757. sortable._mouseStop( event, true );
  758. // Restore sortable behaviors that were modfied
  759. // when the draggable entered the sortable area (#9481)
  760. sortable.options.revert = sortable.options._revert;
  761. sortable.options.helper = sortable.options._helper;
  762. if ( sortable.placeholder ) {
  763. sortable.placeholder.remove();
  764. }
  765. // Restore and recalculate the draggable's offset considering the sortable
  766. // may have modified them in unexpected ways. (#8809, #10669)
  767. ui.helper.appendTo( draggable._parent );
  768. draggable._refreshOffsets( event );
  769. ui.position = draggable._generatePosition( event, true );
  770. draggable._trigger( "fromSortable", event );
  771. // Inform draggable that the helper is no longer in a valid drop zone
  772. draggable.dropped = false;
  773. // Need to refreshPositions of all sortables just in case removing
  774. // from one sortable changes the location of other sortables (#9675)
  775. $.each( draggable.sortables, function() {
  776. this.refreshPositions();
  777. } );
  778. }
  779. }
  780. } );
  781. }
  782. } );
  783. $.ui.plugin.add( "draggable", "cursor", {
  784. start: function( event, ui, instance ) {
  785. var t = $( "body" ),
  786. o = instance.options;
  787. if ( t.css( "cursor" ) ) {
  788. o._cursor = t.css( "cursor" );
  789. }
  790. t.css( "cursor", o.cursor );
  791. },
  792. stop: function( event, ui, instance ) {
  793. var o = instance.options;
  794. if ( o._cursor ) {
  795. $( "body" ).css( "cursor", o._cursor );
  796. }
  797. }
  798. } );
  799. $.ui.plugin.add( "draggable", "opacity", {
  800. start: function( event, ui, instance ) {
  801. var t = $( ui.helper ),
  802. o = instance.options;
  803. if ( t.css( "opacity" ) ) {
  804. o._opacity = t.css( "opacity" );
  805. }
  806. t.css( "opacity", o.opacity );
  807. },
  808. stop: function( event, ui, instance ) {
  809. var o = instance.options;
  810. if ( o._opacity ) {
  811. $( ui.helper ).css( "opacity", o._opacity );
  812. }
  813. }
  814. } );
  815. $.ui.plugin.add( "draggable", "scroll", {
  816. start: function( event, ui, i ) {
  817. if ( !i.scrollParentNotHidden ) {
  818. i.scrollParentNotHidden = i.helper.scrollParent( false );
  819. }
  820. if ( i.scrollParentNotHidden[ 0 ] !== i.document[ 0 ] &&
  821. i.scrollParentNotHidden[ 0 ].tagName !== "HTML" ) {
  822. i.overflowOffset = i.scrollParentNotHidden.offset();
  823. }
  824. },
  825. drag: function( event, ui, i ) {
  826. var o = i.options,
  827. scrolled = false,
  828. scrollParent = i.scrollParentNotHidden[ 0 ],
  829. document = i.document[ 0 ];
  830. if ( scrollParent !== document && scrollParent.tagName !== "HTML" ) {
  831. if ( !o.axis || o.axis !== "x" ) {
  832. if ( ( i.overflowOffset.top + scrollParent.offsetHeight ) - event.pageY <
  833. o.scrollSensitivity ) {
  834. scrollParent.scrollTop = scrolled = scrollParent.scrollTop + o.scrollSpeed;
  835. } else if ( event.pageY - i.overflowOffset.top < o.scrollSensitivity ) {
  836. scrollParent.scrollTop = scrolled = scrollParent.scrollTop - o.scrollSpeed;
  837. }
  838. }
  839. if ( !o.axis || o.axis !== "y" ) {
  840. if ( ( i.overflowOffset.left + scrollParent.offsetWidth ) - event.pageX <
  841. o.scrollSensitivity ) {
  842. scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft + o.scrollSpeed;
  843. } else if ( event.pageX - i.overflowOffset.left < o.scrollSensitivity ) {
  844. scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft - o.scrollSpeed;
  845. }
  846. }
  847. } else {
  848. if ( !o.axis || o.axis !== "x" ) {
  849. if ( event.pageY - $( document ).scrollTop() < o.scrollSensitivity ) {
  850. scrolled = $( document ).scrollTop( $( document ).scrollTop() - o.scrollSpeed );
  851. } else if ( $( window ).height() - ( event.pageY - $( document ).scrollTop() ) <
  852. o.scrollSensitivity ) {
  853. scrolled = $( document ).scrollTop( $( document ).scrollTop() + o.scrollSpeed );
  854. }
  855. }
  856. if ( !o.axis || o.axis !== "y" ) {
  857. if ( event.pageX - $( document ).scrollLeft() < o.scrollSensitivity ) {
  858. scrolled = $( document ).scrollLeft(
  859. $( document ).scrollLeft() - o.scrollSpeed
  860. );
  861. } else if ( $( window ).width() - ( event.pageX - $( document ).scrollLeft() ) <
  862. o.scrollSensitivity ) {
  863. scrolled = $( document ).scrollLeft(
  864. $( document ).scrollLeft() + o.scrollSpeed
  865. );
  866. }
  867. }
  868. }
  869. if ( scrolled !== false && $.ui.ddmanager && !o.dropBehaviour ) {
  870. $.ui.ddmanager.prepareOffsets( i, event );
  871. }
  872. }
  873. } );
  874. $.ui.plugin.add( "draggable", "snap", {
  875. start: function( event, ui, i ) {
  876. var o = i.options;
  877. i.snapElements = [];
  878. $( o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap )
  879. .each( function() {
  880. var $t = $( this ),
  881. $o = $t.offset();
  882. if ( this !== i.element[ 0 ] ) {
  883. i.snapElements.push( {
  884. item: this,
  885. width: $t.outerWidth(), height: $t.outerHeight(),
  886. top: $o.top, left: $o.left
  887. } );
  888. }
  889. } );
  890. },
  891. drag: function( event, ui, inst ) {
  892. var ts, bs, ls, rs, l, r, t, b, i, first,
  893. o = inst.options,
  894. d = o.snapTolerance,
  895. x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
  896. y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;
  897. for ( i = inst.snapElements.length - 1; i >= 0; i-- ) {
  898. l = inst.snapElements[ i ].left - inst.margins.left;
  899. r = l + inst.snapElements[ i ].width;
  900. t = inst.snapElements[ i ].top - inst.margins.top;
  901. b = t + inst.snapElements[ i ].height;
  902. if ( x2 < l - d || x1 > r + d || y2 < t - d || y1 > b + d ||
  903. !$.contains( inst.snapElements[ i ].item.ownerDocument,
  904. inst.snapElements[ i ].item ) ) {
  905. if ( inst.snapElements[ i ].snapping ) {
  906. ( inst.options.snap.release &&
  907. inst.options.snap.release.call(
  908. inst.element,
  909. event,
  910. $.extend( inst._uiHash(), { snapItem: inst.snapElements[ i ].item } )
  911. ) );
  912. }
  913. inst.snapElements[ i ].snapping = false;
  914. continue;
  915. }
  916. if ( o.snapMode !== "inner" ) {
  917. ts = Math.abs( t - y2 ) <= d;
  918. bs = Math.abs( b - y1 ) <= d;
  919. ls = Math.abs( l - x2 ) <= d;
  920. rs = Math.abs( r - x1 ) <= d;
  921. if ( ts ) {
  922. ui.position.top = inst._convertPositionTo( "relative", {
  923. top: t - inst.helperProportions.height,
  924. left: 0
  925. } ).top;
  926. }
  927. if ( bs ) {
  928. ui.position.top = inst._convertPositionTo( "relative", {
  929. top: b,
  930. left: 0
  931. } ).top;
  932. }
  933. if ( ls ) {
  934. ui.position.left = inst._convertPositionTo( "relative", {
  935. top: 0,
  936. left: l - inst.helperProportions.width
  937. } ).left;
  938. }
  939. if ( rs ) {
  940. ui.position.left = inst._convertPositionTo( "relative", {
  941. top: 0,
  942. left: r
  943. } ).left;
  944. }
  945. }
  946. first = ( ts || bs || ls || rs );
  947. if ( o.snapMode !== "outer" ) {
  948. ts = Math.abs( t - y1 ) <= d;
  949. bs = Math.abs( b - y2 ) <= d;
  950. ls = Math.abs( l - x1 ) <= d;
  951. rs = Math.abs( r - x2 ) <= d;
  952. if ( ts ) {
  953. ui.position.top = inst._convertPositionTo( "relative", {
  954. top: t,
  955. left: 0
  956. } ).top;
  957. }
  958. if ( bs ) {
  959. ui.position.top = inst._convertPositionTo( "relative", {
  960. top: b - inst.helperProportions.height,
  961. left: 0
  962. } ).top;
  963. }
  964. if ( ls ) {
  965. ui.position.left = inst._convertPositionTo( "relative", {
  966. top: 0,
  967. left: l
  968. } ).left;
  969. }
  970. if ( rs ) {
  971. ui.position.left = inst._convertPositionTo( "relative", {
  972. top: 0,
  973. left: r - inst.helperProportions.width
  974. } ).left;
  975. }
  976. }
  977. if ( !inst.snapElements[ i ].snapping && ( ts || bs || ls || rs || first ) ) {
  978. ( inst.options.snap.snap &&
  979. inst.options.snap.snap.call(
  980. inst.element,
  981. event,
  982. $.extend( inst._uiHash(), {
  983. snapItem: inst.snapElements[ i ].item
  984. } ) ) );
  985. }
  986. inst.snapElements[ i ].snapping = ( ts || bs || ls || rs || first );
  987. }
  988. }
  989. } );
  990. $.ui.plugin.add( "draggable", "stack", {
  991. start: function( event, ui, instance ) {
  992. var min,
  993. o = instance.options,
  994. group = $.makeArray( $( o.stack ) ).sort( function( a, b ) {
  995. return ( parseInt( $( a ).css( "zIndex" ), 10 ) || 0 ) -
  996. ( parseInt( $( b ).css( "zIndex" ), 10 ) || 0 );
  997. } );
  998. if ( !group.length ) { return; }
  999. min = parseInt( $( group[ 0 ] ).css( "zIndex" ), 10 ) || 0;
  1000. $( group ).each( function( i ) {
  1001. $( this ).css( "zIndex", min + i );
  1002. } );
  1003. this.css( "zIndex", ( min + group.length ) );
  1004. }
  1005. } );
  1006. $.ui.plugin.add( "draggable", "zIndex", {
  1007. start: function( event, ui, instance ) {
  1008. var t = $( ui.helper ),
  1009. o = instance.options;
  1010. if ( t.css( "zIndex" ) ) {
  1011. o._zIndex = t.css( "zIndex" );
  1012. }
  1013. t.css( "zIndex", o.zIndex );
  1014. },
  1015. stop: function( event, ui, instance ) {
  1016. var o = instance.options;
  1017. if ( o._zIndex ) {
  1018. $( ui.helper ).css( "zIndex", o._zIndex );
  1019. }
  1020. }
  1021. } );
  1022. return $.ui.draggable;
  1023. } ) );