position.js 15 KB

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