jquery.ui.position.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. /*! jQuery UI - v1.12.1 - 2016-09-16
  2. * http://jqueryui.com
  3. * Includes: position.js
  4. * Copyright jQuery Foundation and other contributors; Licensed MIT */
  5. (function( factory ) {
  6. if ( typeof define === "function" && define.amd ) {
  7. // AMD. Register as an anonymous module.
  8. define([ "jquery" ], factory );
  9. } else {
  10. // Browser globals
  11. factory( jQuery );
  12. }
  13. }(function( $ ) {
  14. $.ui = $.ui || {};
  15. var version = $.ui.version = "1.12.1";
  16. /*!
  17. * jQuery UI Position 1.12.1
  18. * http://jqueryui.com
  19. *
  20. * Copyright jQuery Foundation and other contributors
  21. * Released under the MIT license.
  22. * http://jquery.org/license
  23. *
  24. * http://api.jqueryui.com/position/
  25. */
  26. //>>label: Position
  27. //>>group: Core
  28. //>>description: Positions elements relative to other elements.
  29. //>>docs: http://api.jqueryui.com/position/
  30. //>>demos: http://jqueryui.com/position/
  31. ( function() {
  32. var cachedScrollbarWidth,
  33. max = Math.max,
  34. abs = Math.abs,
  35. rhorizontal = /left|center|right/,
  36. rvertical = /top|center|bottom/,
  37. roffset = /[\+\-]\d+(\.[\d]+)?%?/,
  38. rposition = /^\w+/,
  39. rpercent = /%$/,
  40. _position = $.fn.position;
  41. function getOffsets( offsets, width, height ) {
  42. return [
  43. parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
  44. parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
  45. ];
  46. }
  47. function parseCss( element, property ) {
  48. return parseInt( $.css( element, property ), 10 ) || 0;
  49. }
  50. function getDimensions( elem ) {
  51. var raw = elem[ 0 ];
  52. if ( raw.nodeType === 9 ) {
  53. return {
  54. width: elem.width(),
  55. height: elem.height(),
  56. offset: { top: 0, left: 0 }
  57. };
  58. }
  59. if ( $.isWindow( raw ) ) {
  60. return {
  61. width: elem.width(),
  62. height: elem.height(),
  63. offset: { top: elem.scrollTop(), left: elem.scrollLeft() }
  64. };
  65. }
  66. if ( raw.preventDefault ) {
  67. return {
  68. width: 0,
  69. height: 0,
  70. offset: { top: raw.pageY, left: raw.pageX }
  71. };
  72. }
  73. return {
  74. width: elem.outerWidth(),
  75. height: elem.outerHeight(),
  76. offset: elem.offset()
  77. };
  78. }
  79. $.position = {
  80. scrollbarWidth: function() {
  81. if ( cachedScrollbarWidth !== undefined ) {
  82. return cachedScrollbarWidth;
  83. }
  84. var w1, w2,
  85. div = $( "<div " +
  86. "style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'>" +
  87. "<div style='height:100px;width:auto;'></div></div>" ),
  88. innerDiv = div.children()[ 0 ];
  89. $( "body" ).append( div );
  90. w1 = innerDiv.offsetWidth;
  91. div.css( "overflow", "scroll" );
  92. w2 = innerDiv.offsetWidth;
  93. if ( w1 === w2 ) {
  94. w2 = div[ 0 ].clientWidth;
  95. }
  96. div.remove();
  97. return ( cachedScrollbarWidth = w1 - w2 );
  98. },
  99. getScrollInfo: function( within ) {
  100. var overflowX = within.isWindow || within.isDocument ? "" :
  101. within.element.css( "overflow-x" ),
  102. overflowY = within.isWindow || within.isDocument ? "" :
  103. within.element.css( "overflow-y" ),
  104. hasOverflowX = overflowX === "scroll" ||
  105. ( overflowX === "auto" && within.width < within.element[ 0 ].scrollWidth ),
  106. hasOverflowY = overflowY === "scroll" ||
  107. ( overflowY === "auto" && within.height < within.element[ 0 ].scrollHeight );
  108. return {
  109. width: hasOverflowY ? $.position.scrollbarWidth() : 0,
  110. height: hasOverflowX ? $.position.scrollbarWidth() : 0
  111. };
  112. },
  113. getWithinInfo: function( element ) {
  114. var withinElement = $( element || window ),
  115. isWindow = $.isWindow( withinElement[ 0 ] ),
  116. isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9,
  117. hasOffset = !isWindow && !isDocument;
  118. return {
  119. element: withinElement,
  120. isWindow: isWindow,
  121. isDocument: isDocument,
  122. offset: hasOffset ? $( element ).offset() : { left: 0, top: 0 },
  123. scrollLeft: withinElement.scrollLeft(),
  124. scrollTop: withinElement.scrollTop(),
  125. width: withinElement.outerWidth(),
  126. height: withinElement.outerHeight()
  127. };
  128. }
  129. };
  130. $.fn.position = function( options ) {
  131. if ( !options || !options.of ) {
  132. return _position.apply( this, arguments );
  133. }
  134. // Make a copy, we don't want to modify arguments
  135. options = $.extend( {}, options );
  136. var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,
  137. target = $( options.of ),
  138. within = $.position.getWithinInfo( options.within ),
  139. scrollInfo = $.position.getScrollInfo( within ),
  140. collision = ( options.collision || "flip" ).split( " " ),
  141. offsets = {};
  142. dimensions = getDimensions( target );
  143. if ( target[ 0 ].preventDefault ) {
  144. // Force left top to allow flipping
  145. options.at = "left top";
  146. }
  147. targetWidth = dimensions.width;
  148. targetHeight = dimensions.height;
  149. targetOffset = dimensions.offset;
  150. // Clone to reuse original targetOffset later
  151. basePosition = $.extend( {}, targetOffset );
  152. // Force my and at to have valid horizontal and vertical positions
  153. // if a value is missing or invalid, it will be converted to center
  154. $.each( [ "my", "at" ], function() {
  155. var pos = ( options[ this ] || "" ).split( " " ),
  156. horizontalOffset,
  157. verticalOffset;
  158. if ( pos.length === 1 ) {
  159. pos = rhorizontal.test( pos[ 0 ] ) ?
  160. pos.concat( [ "center" ] ) :
  161. rvertical.test( pos[ 0 ] ) ?
  162. [ "center" ].concat( pos ) :
  163. [ "center", "center" ];
  164. }
  165. pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
  166. pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";
  167. // Calculate offsets
  168. horizontalOffset = roffset.exec( pos[ 0 ] );
  169. verticalOffset = roffset.exec( pos[ 1 ] );
  170. offsets[ this ] = [
  171. horizontalOffset ? horizontalOffset[ 0 ] : 0,
  172. verticalOffset ? verticalOffset[ 0 ] : 0
  173. ];
  174. // Reduce to just the positions without the offsets
  175. options[ this ] = [
  176. rposition.exec( pos[ 0 ] )[ 0 ],
  177. rposition.exec( pos[ 1 ] )[ 0 ]
  178. ];
  179. } );
  180. // Normalize collision option
  181. if ( collision.length === 1 ) {
  182. collision[ 1 ] = collision[ 0 ];
  183. }
  184. if ( options.at[ 0 ] === "right" ) {
  185. basePosition.left += targetWidth;
  186. } else if ( options.at[ 0 ] === "center" ) {
  187. basePosition.left += targetWidth / 2;
  188. }
  189. if ( options.at[ 1 ] === "bottom" ) {
  190. basePosition.top += targetHeight;
  191. } else if ( options.at[ 1 ] === "center" ) {
  192. basePosition.top += targetHeight / 2;
  193. }
  194. atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
  195. basePosition.left += atOffset[ 0 ];
  196. basePosition.top += atOffset[ 1 ];
  197. return this.each( function() {
  198. var collisionPosition, using,
  199. elem = $( this ),
  200. elemWidth = elem.outerWidth(),
  201. elemHeight = elem.outerHeight(),
  202. marginLeft = parseCss( this, "marginLeft" ),
  203. marginTop = parseCss( this, "marginTop" ),
  204. collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) +
  205. scrollInfo.width,
  206. collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) +
  207. scrollInfo.height,
  208. position = $.extend( {}, basePosition ),
  209. myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );
  210. if ( options.my[ 0 ] === "right" ) {
  211. position.left -= elemWidth;
  212. } else if ( options.my[ 0 ] === "center" ) {
  213. position.left -= elemWidth / 2;
  214. }
  215. if ( options.my[ 1 ] === "bottom" ) {
  216. position.top -= elemHeight;
  217. } else if ( options.my[ 1 ] === "center" ) {
  218. position.top -= elemHeight / 2;
  219. }
  220. position.left += myOffset[ 0 ];
  221. position.top += myOffset[ 1 ];
  222. collisionPosition = {
  223. marginLeft: marginLeft,
  224. marginTop: marginTop
  225. };
  226. $.each( [ "left", "top" ], function( i, dir ) {
  227. if ( $.ui.position[ collision[ i ] ] ) {
  228. $.ui.position[ collision[ i ] ][ dir ]( position, {
  229. targetWidth: targetWidth,
  230. targetHeight: targetHeight,
  231. elemWidth: elemWidth,
  232. elemHeight: elemHeight,
  233. collisionPosition: collisionPosition,
  234. collisionWidth: collisionWidth,
  235. collisionHeight: collisionHeight,
  236. offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
  237. my: options.my,
  238. at: options.at,
  239. within: within,
  240. elem: elem
  241. } );
  242. }
  243. } );
  244. if ( options.using ) {
  245. // Adds feedback as second argument to using callback, if present
  246. using = function( props ) {
  247. var left = targetOffset.left - position.left,
  248. right = left + targetWidth - elemWidth,
  249. top = targetOffset.top - position.top,
  250. bottom = top + targetHeight - elemHeight,
  251. feedback = {
  252. target: {
  253. element: target,
  254. left: targetOffset.left,
  255. top: targetOffset.top,
  256. width: targetWidth,
  257. height: targetHeight
  258. },
  259. element: {
  260. element: elem,
  261. left: position.left,
  262. top: position.top,
  263. width: elemWidth,
  264. height: elemHeight
  265. },
  266. horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
  267. vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
  268. };
  269. if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
  270. feedback.horizontal = "center";
  271. }
  272. if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
  273. feedback.vertical = "middle";
  274. }
  275. if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
  276. feedback.important = "horizontal";
  277. } else {
  278. feedback.important = "vertical";
  279. }
  280. options.using.call( this, props, feedback );
  281. };
  282. }
  283. elem.offset( $.extend( position, { using: using } ) );
  284. } );
  285. };
  286. $.ui.position = {
  287. fit: {
  288. left: function( position, data ) {
  289. var within = data.within,
  290. withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
  291. outerWidth = within.width,
  292. collisionPosLeft = position.left - data.collisionPosition.marginLeft,
  293. overLeft = withinOffset - collisionPosLeft,
  294. overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
  295. newOverRight;
  296. // Element is wider than within
  297. if ( data.collisionWidth > outerWidth ) {
  298. // Element is initially over the left side of within
  299. if ( overLeft > 0 && overRight <= 0 ) {
  300. newOverRight = position.left + overLeft + data.collisionWidth - outerWidth -
  301. withinOffset;
  302. position.left += overLeft - newOverRight;
  303. // Element is initially over right side of within
  304. } else if ( overRight > 0 && overLeft <= 0 ) {
  305. position.left = withinOffset;
  306. // Element is initially over both left and right sides of within
  307. } else {
  308. if ( overLeft > overRight ) {
  309. position.left = withinOffset + outerWidth - data.collisionWidth;
  310. } else {
  311. position.left = withinOffset;
  312. }
  313. }
  314. // Too far left -> align with left edge
  315. } else if ( overLeft > 0 ) {
  316. position.left += overLeft;
  317. // Too far right -> align with right edge
  318. } else if ( overRight > 0 ) {
  319. position.left -= overRight;
  320. // Adjust based on position and margin
  321. } else {
  322. position.left = max( position.left - collisionPosLeft, position.left );
  323. }
  324. },
  325. top: function( position, data ) {
  326. var within = data.within,
  327. withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
  328. outerHeight = data.within.height,
  329. collisionPosTop = position.top - data.collisionPosition.marginTop,
  330. overTop = withinOffset - collisionPosTop,
  331. overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
  332. newOverBottom;
  333. // Element is taller than within
  334. if ( data.collisionHeight > outerHeight ) {
  335. // Element is initially over the top of within
  336. if ( overTop > 0 && overBottom <= 0 ) {
  337. newOverBottom = position.top + overTop + data.collisionHeight - outerHeight -
  338. withinOffset;
  339. position.top += overTop - newOverBottom;
  340. // Element is initially over bottom of within
  341. } else if ( overBottom > 0 && overTop <= 0 ) {
  342. position.top = withinOffset;
  343. // Element is initially over both top and bottom of within
  344. } else {
  345. if ( overTop > overBottom ) {
  346. position.top = withinOffset + outerHeight - data.collisionHeight;
  347. } else {
  348. position.top = withinOffset;
  349. }
  350. }
  351. // Too far up -> align with top
  352. } else if ( overTop > 0 ) {
  353. position.top += overTop;
  354. // Too far down -> align with bottom edge
  355. } else if ( overBottom > 0 ) {
  356. position.top -= overBottom;
  357. // Adjust based on position and margin
  358. } else {
  359. position.top = max( position.top - collisionPosTop, position.top );
  360. }
  361. }
  362. },
  363. flip: {
  364. left: function( position, data ) {
  365. var within = data.within,
  366. withinOffset = within.offset.left + within.scrollLeft,
  367. outerWidth = within.width,
  368. offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
  369. collisionPosLeft = position.left - data.collisionPosition.marginLeft,
  370. overLeft = collisionPosLeft - offsetLeft,
  371. overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
  372. myOffset = data.my[ 0 ] === "left" ?
  373. -data.elemWidth :
  374. data.my[ 0 ] === "right" ?
  375. data.elemWidth :
  376. 0,
  377. atOffset = data.at[ 0 ] === "left" ?
  378. data.targetWidth :
  379. data.at[ 0 ] === "right" ?
  380. -data.targetWidth :
  381. 0,
  382. offset = -2 * data.offset[ 0 ],
  383. newOverRight,
  384. newOverLeft;
  385. if ( overLeft < 0 ) {
  386. newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth -
  387. outerWidth - withinOffset;
  388. if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
  389. position.left += myOffset + atOffset + offset;
  390. }
  391. } else if ( overRight > 0 ) {
  392. newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset +
  393. atOffset + offset - offsetLeft;
  394. if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
  395. position.left += myOffset + atOffset + offset;
  396. }
  397. }
  398. },
  399. top: function( position, data ) {
  400. var within = data.within,
  401. withinOffset = within.offset.top + within.scrollTop,
  402. outerHeight = within.height,
  403. offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
  404. collisionPosTop = position.top - data.collisionPosition.marginTop,
  405. overTop = collisionPosTop - offsetTop,
  406. overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
  407. top = data.my[ 1 ] === "top",
  408. myOffset = top ?
  409. -data.elemHeight :
  410. data.my[ 1 ] === "bottom" ?
  411. data.elemHeight :
  412. 0,
  413. atOffset = data.at[ 1 ] === "top" ?
  414. data.targetHeight :
  415. data.at[ 1 ] === "bottom" ?
  416. -data.targetHeight :
  417. 0,
  418. offset = -2 * data.offset[ 1 ],
  419. newOverTop,
  420. newOverBottom;
  421. if ( overTop < 0 ) {
  422. newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight -
  423. outerHeight - withinOffset;
  424. if ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) {
  425. position.top += myOffset + atOffset + offset;
  426. }
  427. } else if ( overBottom > 0 ) {
  428. newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset +
  429. offset - offsetTop;
  430. if ( newOverTop > 0 || abs( newOverTop ) < overBottom ) {
  431. position.top += myOffset + atOffset + offset;
  432. }
  433. }
  434. }
  435. },
  436. flipfit: {
  437. left: function() {
  438. $.ui.position.flip.left.apply( this, arguments );
  439. $.ui.position.fit.left.apply( this, arguments );
  440. },
  441. top: function() {
  442. $.ui.position.flip.top.apply( this, arguments );
  443. $.ui.position.fit.top.apply( this, arguments );
  444. }
  445. }
  446. };
  447. } )();
  448. var position = $.ui.position;
  449. }));