position.js 16 KB

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