positioning.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. // Copyright 2006 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Common positioning code.
  16. *
  17. * @author eae@google.com (Emil A Eklund)
  18. */
  19. goog.provide('goog.positioning');
  20. goog.provide('goog.positioning.Corner');
  21. goog.provide('goog.positioning.CornerBit');
  22. goog.provide('goog.positioning.Overflow');
  23. goog.provide('goog.positioning.OverflowStatus');
  24. goog.require('goog.asserts');
  25. goog.require('goog.dom');
  26. goog.require('goog.dom.TagName');
  27. goog.require('goog.math.Coordinate');
  28. goog.require('goog.math.Rect');
  29. goog.require('goog.math.Size');
  30. goog.require('goog.style');
  31. goog.require('goog.style.bidi');
  32. /**
  33. * Enum for bits in the {@see goog.positioning.Corner) bitmap.
  34. *
  35. * @enum {number}
  36. */
  37. goog.positioning.CornerBit = {
  38. BOTTOM: 1,
  39. CENTER: 2,
  40. RIGHT: 4,
  41. FLIP_RTL: 8
  42. };
  43. /**
  44. * Enum for representing an element corner for positioning the popup.
  45. *
  46. * The START constants map to LEFT if element directionality is left
  47. * to right and RIGHT if the directionality is right to left.
  48. * Likewise END maps to RIGHT or LEFT depending on the directionality.
  49. *
  50. * @enum {number}
  51. */
  52. goog.positioning.Corner = {
  53. TOP_LEFT: 0,
  54. TOP_RIGHT: goog.positioning.CornerBit.RIGHT,
  55. BOTTOM_LEFT: goog.positioning.CornerBit.BOTTOM,
  56. BOTTOM_RIGHT:
  57. goog.positioning.CornerBit.BOTTOM | goog.positioning.CornerBit.RIGHT,
  58. TOP_START: goog.positioning.CornerBit.FLIP_RTL,
  59. TOP_END:
  60. goog.positioning.CornerBit.FLIP_RTL | goog.positioning.CornerBit.RIGHT,
  61. BOTTOM_START:
  62. goog.positioning.CornerBit.BOTTOM | goog.positioning.CornerBit.FLIP_RTL,
  63. BOTTOM_END: goog.positioning.CornerBit.BOTTOM |
  64. goog.positioning.CornerBit.RIGHT | goog.positioning.CornerBit.FLIP_RTL,
  65. TOP_CENTER: goog.positioning.CornerBit.CENTER,
  66. BOTTOM_CENTER:
  67. goog.positioning.CornerBit.BOTTOM | goog.positioning.CornerBit.CENTER
  68. };
  69. /**
  70. * Enum for representing position handling in cases where the element would be
  71. * positioned outside the viewport.
  72. *
  73. * @enum {number}
  74. */
  75. goog.positioning.Overflow = {
  76. /** Ignore overflow */
  77. IGNORE: 0,
  78. /** Try to fit horizontally in the viewport at all costs. */
  79. ADJUST_X: 1,
  80. /** If the element can't fit horizontally, report positioning failure. */
  81. FAIL_X: 2,
  82. /** Try to fit vertically in the viewport at all costs. */
  83. ADJUST_Y: 4,
  84. /** If the element can't fit vertically, report positioning failure. */
  85. FAIL_Y: 8,
  86. /** Resize the element's width to fit in the viewport. */
  87. RESIZE_WIDTH: 16,
  88. /** Resize the element's height to fit in the viewport. */
  89. RESIZE_HEIGHT: 32,
  90. /**
  91. * If the anchor goes off-screen in the x-direction, position the movable
  92. * element off-screen. Otherwise, try to fit horizontally in the viewport.
  93. */
  94. ADJUST_X_EXCEPT_OFFSCREEN: 64 | 1,
  95. /**
  96. * If the anchor goes off-screen in the y-direction, position the movable
  97. * element off-screen. Otherwise, try to fit vertically in the viewport.
  98. */
  99. ADJUST_Y_EXCEPT_OFFSCREEN: 128 | 4
  100. };
  101. /**
  102. * Enum for representing the outcome of a positioning call.
  103. *
  104. * @enum {number}
  105. */
  106. goog.positioning.OverflowStatus = {
  107. NONE: 0,
  108. ADJUSTED_X: 1,
  109. ADJUSTED_Y: 2,
  110. WIDTH_ADJUSTED: 4,
  111. HEIGHT_ADJUSTED: 8,
  112. FAILED_LEFT: 16,
  113. FAILED_RIGHT: 32,
  114. FAILED_TOP: 64,
  115. FAILED_BOTTOM: 128,
  116. FAILED_OUTSIDE_VIEWPORT: 256
  117. };
  118. /**
  119. * Shorthand to check if a status code contains any fail code.
  120. * @type {number}
  121. */
  122. goog.positioning.OverflowStatus.FAILED =
  123. goog.positioning.OverflowStatus.FAILED_LEFT |
  124. goog.positioning.OverflowStatus.FAILED_RIGHT |
  125. goog.positioning.OverflowStatus.FAILED_TOP |
  126. goog.positioning.OverflowStatus.FAILED_BOTTOM |
  127. goog.positioning.OverflowStatus.FAILED_OUTSIDE_VIEWPORT;
  128. /**
  129. * Shorthand to check if horizontal positioning failed.
  130. * @type {number}
  131. */
  132. goog.positioning.OverflowStatus.FAILED_HORIZONTAL =
  133. goog.positioning.OverflowStatus.FAILED_LEFT |
  134. goog.positioning.OverflowStatus.FAILED_RIGHT;
  135. /**
  136. * Shorthand to check if vertical positioning failed.
  137. * @type {number}
  138. */
  139. goog.positioning.OverflowStatus.FAILED_VERTICAL =
  140. goog.positioning.OverflowStatus.FAILED_TOP |
  141. goog.positioning.OverflowStatus.FAILED_BOTTOM;
  142. /**
  143. * Positions a movable element relative to an anchor element. The caller
  144. * specifies the corners that should touch. This functions then moves the
  145. * movable element accordingly.
  146. *
  147. * @param {Element} anchorElement The element that is the anchor for where
  148. * the movable element should position itself.
  149. * @param {goog.positioning.Corner} anchorElementCorner The corner of the
  150. * anchorElement for positioning the movable element.
  151. * @param {Element} movableElement The element to move.
  152. * @param {goog.positioning.Corner} movableElementCorner The corner of the
  153. * movableElement that that should be positioned adjacent to the anchor
  154. * element.
  155. * @param {goog.math.Coordinate=} opt_offset An offset specified in pixels.
  156. * After the normal positioning algorithm is applied, the offset is then
  157. * applied. Positive coordinates move the popup closer to the center of the
  158. * anchor element. Negative coordinates move the popup away from the center
  159. * of the anchor element.
  160. * @param {goog.math.Box=} opt_margin A margin specified in pixels.
  161. * After the normal positioning algorithm is applied and any offset, the
  162. * margin is then applied. Positive coordinates move the popup away from the
  163. * spot it was positioned towards its center. Negative coordinates move it
  164. * towards the spot it was positioned away from its center.
  165. * @param {?number=} opt_overflow Overflow handling mode. Defaults to IGNORE if
  166. * not specified. Bitmap, {@see goog.positioning.Overflow}.
  167. * @param {goog.math.Size=} opt_preferredSize The preferred size of the
  168. * movableElement.
  169. * @param {goog.math.Box=} opt_viewport Box object describing the dimensions of
  170. * the viewport. The viewport is specified relative to offsetParent of
  171. * {@code movableElement}. In other words, the viewport can be thought of as
  172. * describing a "position: absolute" element contained in the offsetParent.
  173. * It defaults to visible area of nearest scrollable ancestor of
  174. * {@code movableElement} (see {@code goog.style.getVisibleRectForElement}).
  175. * @return {goog.positioning.OverflowStatus} Status bitmap,
  176. * {@see goog.positioning.OverflowStatus}.
  177. */
  178. goog.positioning.positionAtAnchor = function(
  179. anchorElement, anchorElementCorner, movableElement, movableElementCorner,
  180. opt_offset, opt_margin, opt_overflow, opt_preferredSize, opt_viewport) {
  181. goog.asserts.assert(movableElement);
  182. var movableParentTopLeft =
  183. goog.positioning.getOffsetParentPageOffset(movableElement);
  184. // Get the visible part of the anchor element. anchorRect is
  185. // relative to anchorElement's page.
  186. var anchorRect = goog.positioning.getVisiblePart_(anchorElement);
  187. // Translate anchorRect to be relative to movableElement's page.
  188. goog.style.translateRectForAnotherFrame(
  189. anchorRect, goog.dom.getDomHelper(anchorElement),
  190. goog.dom.getDomHelper(movableElement));
  191. // Offset based on which corner of the element we want to position against.
  192. var corner =
  193. goog.positioning.getEffectiveCorner(anchorElement, anchorElementCorner);
  194. var offsetLeft = anchorRect.left;
  195. if (corner & goog.positioning.CornerBit.RIGHT) {
  196. offsetLeft += anchorRect.width;
  197. } else if (corner & goog.positioning.CornerBit.CENTER) {
  198. offsetLeft += anchorRect.width / 2;
  199. }
  200. // absolutePos is a candidate position relative to the
  201. // movableElement's window.
  202. var absolutePos = new goog.math.Coordinate(
  203. offsetLeft, anchorRect.top +
  204. (corner & goog.positioning.CornerBit.BOTTOM ? anchorRect.height : 0));
  205. // Translate absolutePos to be relative to the offsetParent.
  206. absolutePos =
  207. goog.math.Coordinate.difference(absolutePos, movableParentTopLeft);
  208. // Apply offset, if specified
  209. if (opt_offset) {
  210. absolutePos.x +=
  211. (corner & goog.positioning.CornerBit.RIGHT ? -1 : 1) * opt_offset.x;
  212. absolutePos.y +=
  213. (corner & goog.positioning.CornerBit.BOTTOM ? -1 : 1) * opt_offset.y;
  214. }
  215. // Determine dimension of viewport.
  216. var viewport;
  217. if (opt_overflow) {
  218. if (opt_viewport) {
  219. viewport = opt_viewport;
  220. } else {
  221. viewport = goog.style.getVisibleRectForElement(movableElement);
  222. if (viewport) {
  223. viewport.top -= movableParentTopLeft.y;
  224. viewport.right -= movableParentTopLeft.x;
  225. viewport.bottom -= movableParentTopLeft.y;
  226. viewport.left -= movableParentTopLeft.x;
  227. }
  228. }
  229. }
  230. return goog.positioning.positionAtCoordinate(
  231. absolutePos, movableElement, movableElementCorner, opt_margin, viewport,
  232. opt_overflow, opt_preferredSize);
  233. };
  234. /**
  235. * Calculates the page offset of the given element's
  236. * offsetParent. This value can be used to translate any x- and
  237. * y-offset relative to the page to an offset relative to the
  238. * offsetParent, which can then be used directly with as position
  239. * coordinate for {@code positionWithCoordinate}.
  240. * @param {!Element} movableElement The element to calculate.
  241. * @return {!goog.math.Coordinate} The page offset, may be (0, 0).
  242. */
  243. goog.positioning.getOffsetParentPageOffset = function(movableElement) {
  244. // Ignore offset for the BODY element unless its position is non-static.
  245. // For cases where the offset parent is HTML rather than the BODY (such as in
  246. // IE strict mode) there's no need to get the position of the BODY as it
  247. // doesn't affect the page offset.
  248. var movableParentTopLeft;
  249. var parent = /** @type {?} */ (movableElement).offsetParent;
  250. if (parent) {
  251. var isBody = parent.tagName == goog.dom.TagName.HTML ||
  252. parent.tagName == goog.dom.TagName.BODY;
  253. if (!isBody || goog.style.getComputedPosition(parent) != 'static') {
  254. // Get the top-left corner of the parent, in page coordinates.
  255. movableParentTopLeft = goog.style.getPageOffset(parent);
  256. if (!isBody) {
  257. movableParentTopLeft = goog.math.Coordinate.difference(
  258. movableParentTopLeft,
  259. new goog.math.Coordinate(
  260. goog.style.bidi.getScrollLeft(parent), parent.scrollTop));
  261. }
  262. }
  263. }
  264. return movableParentTopLeft || new goog.math.Coordinate();
  265. };
  266. /**
  267. * Returns intersection of the specified element and
  268. * goog.style.getVisibleRectForElement for it.
  269. *
  270. * @param {Element} el The target element.
  271. * @return {!goog.math.Rect} Intersection of getVisibleRectForElement
  272. * and the current bounding rectangle of the element. If the
  273. * intersection is empty, returns the bounding rectangle.
  274. * @private
  275. */
  276. goog.positioning.getVisiblePart_ = function(el) {
  277. var rect = goog.style.getBounds(el);
  278. var visibleBox = goog.style.getVisibleRectForElement(el);
  279. if (visibleBox) {
  280. rect.intersection(goog.math.Rect.createFromBox(visibleBox));
  281. }
  282. return rect;
  283. };
  284. /**
  285. * Positions the specified corner of the movable element at the
  286. * specified coordinate.
  287. *
  288. * @param {goog.math.Coordinate} absolutePos The coordinate to position the
  289. * element at.
  290. * @param {Element} movableElement The element to be positioned.
  291. * @param {goog.positioning.Corner} movableElementCorner The corner of the
  292. * movableElement that that should be positioned.
  293. * @param {goog.math.Box=} opt_margin A margin specified in pixels.
  294. * After the normal positioning algorithm is applied and any offset, the
  295. * margin is then applied. Positive coordinates move the popup away from the
  296. * spot it was positioned towards its center. Negative coordinates move it
  297. * towards the spot it was positioned away from its center.
  298. * @param {goog.math.Box=} opt_viewport Box object describing the dimensions of
  299. * the viewport. Required if opt_overflow is specified.
  300. * @param {?number=} opt_overflow Overflow handling mode. Defaults to IGNORE if
  301. * not specified, {@see goog.positioning.Overflow}.
  302. * @param {goog.math.Size=} opt_preferredSize The preferred size of the
  303. * movableElement. Defaults to the current size.
  304. * @return {goog.positioning.OverflowStatus} Status bitmap.
  305. */
  306. goog.positioning.positionAtCoordinate = function(
  307. absolutePos, movableElement, movableElementCorner, opt_margin, opt_viewport,
  308. opt_overflow, opt_preferredSize) {
  309. absolutePos = absolutePos.clone();
  310. // Offset based on attached corner and desired margin.
  311. var corner =
  312. goog.positioning.getEffectiveCorner(movableElement, movableElementCorner);
  313. var elementSize = goog.style.getSize(movableElement);
  314. var size =
  315. opt_preferredSize ? opt_preferredSize.clone() : elementSize.clone();
  316. var positionResult = goog.positioning.getPositionAtCoordinate(
  317. absolutePos, size, corner, opt_margin, opt_viewport, opt_overflow);
  318. if (positionResult.status & goog.positioning.OverflowStatus.FAILED) {
  319. return positionResult.status;
  320. }
  321. goog.style.setPosition(movableElement, positionResult.rect.getTopLeft());
  322. size = positionResult.rect.getSize();
  323. if (!goog.math.Size.equals(elementSize, size)) {
  324. goog.style.setBorderBoxSize(movableElement, size);
  325. }
  326. return positionResult.status;
  327. };
  328. /**
  329. * Computes the position for an element to be placed on-screen at the
  330. * specified coordinates. Returns an object containing both the resulting
  331. * rectangle, and the overflow status bitmap.
  332. *
  333. * @param {!goog.math.Coordinate} absolutePos The coordinate to position the
  334. * element at.
  335. * @param {!goog.math.Size} elementSize The size of the element to be
  336. * positioned.
  337. * @param {goog.positioning.Corner} elementCorner The corner of the
  338. * movableElement that that should be positioned.
  339. * @param {goog.math.Box=} opt_margin A margin specified in pixels.
  340. * After the normal positioning algorithm is applied and any offset, the
  341. * margin is then applied. Positive coordinates move the popup away from the
  342. * spot it was positioned towards its center. Negative coordinates move it
  343. * towards the spot it was positioned away from its center.
  344. * @param {goog.math.Box=} opt_viewport Box object describing the dimensions of
  345. * the viewport. Required if opt_overflow is specified.
  346. * @param {?number=} opt_overflow Overflow handling mode. Defaults to IGNORE
  347. * if not specified, {@see goog.positioning.Overflow}.
  348. * @return {{rect:!goog.math.Rect, status:goog.positioning.OverflowStatus}}
  349. * Object containing the computed position and status bitmap.
  350. */
  351. goog.positioning.getPositionAtCoordinate = function(
  352. absolutePos, elementSize, elementCorner, opt_margin, opt_viewport,
  353. opt_overflow) {
  354. absolutePos = absolutePos.clone();
  355. elementSize = elementSize.clone();
  356. var status = goog.positioning.OverflowStatus.NONE;
  357. if (opt_margin || elementCorner != goog.positioning.Corner.TOP_LEFT) {
  358. if (elementCorner & goog.positioning.CornerBit.RIGHT) {
  359. absolutePos.x -= elementSize.width + (opt_margin ? opt_margin.right : 0);
  360. } else if (elementCorner & goog.positioning.CornerBit.CENTER) {
  361. absolutePos.x -= elementSize.width / 2;
  362. } else if (opt_margin) {
  363. absolutePos.x += opt_margin.left;
  364. }
  365. if (elementCorner & goog.positioning.CornerBit.BOTTOM) {
  366. absolutePos.y -=
  367. elementSize.height + (opt_margin ? opt_margin.bottom : 0);
  368. } else if (opt_margin) {
  369. absolutePos.y += opt_margin.top;
  370. }
  371. }
  372. // Adjust position to fit inside viewport.
  373. if (opt_overflow) {
  374. status = opt_viewport ?
  375. goog.positioning.adjustForViewport_(
  376. absolutePos, elementSize, opt_viewport, opt_overflow) :
  377. goog.positioning.OverflowStatus.FAILED_OUTSIDE_VIEWPORT;
  378. }
  379. var rect = new goog.math.Rect(0, 0, 0, 0);
  380. rect.left = absolutePos.x;
  381. rect.top = absolutePos.y;
  382. rect.width = elementSize.width;
  383. rect.height = elementSize.height;
  384. return {rect: rect, status: status};
  385. };
  386. /**
  387. * Adjusts the position and/or size of an element, identified by its position
  388. * and size, to fit inside the viewport. If the position or size of the element
  389. * is adjusted the pos or size objects, respectively, are modified.
  390. *
  391. * @param {goog.math.Coordinate} pos Position of element, updated if the
  392. * position is adjusted.
  393. * @param {goog.math.Size} size Size of element, updated if the size is
  394. * adjusted.
  395. * @param {goog.math.Box} viewport Bounding box describing the viewport.
  396. * @param {number} overflow Overflow handling mode,
  397. * {@see goog.positioning.Overflow}.
  398. * @return {goog.positioning.OverflowStatus} Status bitmap,
  399. * {@see goog.positioning.OverflowStatus}.
  400. * @private
  401. */
  402. goog.positioning.adjustForViewport_ = function(pos, size, viewport, overflow) {
  403. var status = goog.positioning.OverflowStatus.NONE;
  404. var ADJUST_X_EXCEPT_OFFSCREEN =
  405. goog.positioning.Overflow.ADJUST_X_EXCEPT_OFFSCREEN;
  406. var ADJUST_Y_EXCEPT_OFFSCREEN =
  407. goog.positioning.Overflow.ADJUST_Y_EXCEPT_OFFSCREEN;
  408. if ((overflow & ADJUST_X_EXCEPT_OFFSCREEN) == ADJUST_X_EXCEPT_OFFSCREEN &&
  409. (pos.x < viewport.left || pos.x >= viewport.right)) {
  410. overflow &= ~goog.positioning.Overflow.ADJUST_X;
  411. }
  412. if ((overflow & ADJUST_Y_EXCEPT_OFFSCREEN) == ADJUST_Y_EXCEPT_OFFSCREEN &&
  413. (pos.y < viewport.top || pos.y >= viewport.bottom)) {
  414. overflow &= ~goog.positioning.Overflow.ADJUST_Y;
  415. }
  416. // Left edge outside viewport, try to move it.
  417. if (pos.x < viewport.left && overflow & goog.positioning.Overflow.ADJUST_X) {
  418. pos.x = viewport.left;
  419. status |= goog.positioning.OverflowStatus.ADJUSTED_X;
  420. }
  421. // Ensure object is inside the viewport width if required.
  422. if (overflow & goog.positioning.Overflow.RESIZE_WIDTH) {
  423. // Move left edge inside viewport.
  424. var originalX = pos.x;
  425. if (pos.x < viewport.left) {
  426. pos.x = viewport.left;
  427. status |= goog.positioning.OverflowStatus.WIDTH_ADJUSTED;
  428. }
  429. // Shrink width to inside right of viewport.
  430. if (pos.x + size.width > viewport.right) {
  431. // Set the width to be either the new maximum width within the viewport
  432. // or the width originally within the viewport, whichever is less.
  433. size.width = Math.min(
  434. viewport.right - pos.x, originalX + size.width - viewport.left);
  435. size.width = Math.max(size.width, 0);
  436. status |= goog.positioning.OverflowStatus.WIDTH_ADJUSTED;
  437. }
  438. }
  439. // Right edge outside viewport, try to move it.
  440. if (pos.x + size.width > viewport.right &&
  441. overflow & goog.positioning.Overflow.ADJUST_X) {
  442. pos.x = Math.max(viewport.right - size.width, viewport.left);
  443. status |= goog.positioning.OverflowStatus.ADJUSTED_X;
  444. }
  445. // Left or right edge still outside viewport, fail if the FAIL_X option was
  446. // specified, ignore it otherwise.
  447. if (overflow & goog.positioning.Overflow.FAIL_X) {
  448. status |=
  449. (pos.x < viewport.left ? goog.positioning.OverflowStatus.FAILED_LEFT :
  450. 0) |
  451. (pos.x + size.width > viewport.right ?
  452. goog.positioning.OverflowStatus.FAILED_RIGHT :
  453. 0);
  454. }
  455. // Top edge outside viewport, try to move it.
  456. if (pos.y < viewport.top && overflow & goog.positioning.Overflow.ADJUST_Y) {
  457. pos.y = viewport.top;
  458. status |= goog.positioning.OverflowStatus.ADJUSTED_Y;
  459. }
  460. // Ensure object is inside the viewport height if required.
  461. if (overflow & goog.positioning.Overflow.RESIZE_HEIGHT) {
  462. // Move top edge inside viewport.
  463. var originalY = pos.y;
  464. if (pos.y < viewport.top) {
  465. pos.y = viewport.top;
  466. status |= goog.positioning.OverflowStatus.HEIGHT_ADJUSTED;
  467. }
  468. // Shrink height to inside bottom of viewport.
  469. if (pos.y + size.height > viewport.bottom) {
  470. // Set the height to be either the new maximum height within the viewport
  471. // or the height originally within the viewport, whichever is less.
  472. size.height = Math.min(
  473. viewport.bottom - pos.y, originalY + size.height - viewport.top);
  474. size.height = Math.max(size.height, 0);
  475. status |= goog.positioning.OverflowStatus.HEIGHT_ADJUSTED;
  476. }
  477. }
  478. // Bottom edge outside viewport, try to move it.
  479. if (pos.y + size.height > viewport.bottom &&
  480. overflow & goog.positioning.Overflow.ADJUST_Y) {
  481. pos.y = Math.max(viewport.bottom - size.height, viewport.top);
  482. status |= goog.positioning.OverflowStatus.ADJUSTED_Y;
  483. }
  484. // Top or bottom edge still outside viewport, fail if the FAIL_Y option was
  485. // specified, ignore it otherwise.
  486. if (overflow & goog.positioning.Overflow.FAIL_Y) {
  487. status |=
  488. (pos.y < viewport.top ? goog.positioning.OverflowStatus.FAILED_TOP :
  489. 0) |
  490. (pos.y + size.height > viewport.bottom ?
  491. goog.positioning.OverflowStatus.FAILED_BOTTOM :
  492. 0);
  493. }
  494. return status;
  495. };
  496. /**
  497. * Returns an absolute corner (top/bottom left/right) given an absolute
  498. * or relative (top/bottom start/end) corner and the direction of an element.
  499. * Absolute corners remain unchanged.
  500. * @param {Element} element DOM element to test for RTL direction.
  501. * @param {goog.positioning.Corner} corner The popup corner used for
  502. * positioning.
  503. * @return {goog.positioning.Corner} Effective corner.
  504. */
  505. goog.positioning.getEffectiveCorner = function(element, corner) {
  506. return /** @type {goog.positioning.Corner} */ (
  507. (corner & goog.positioning.CornerBit.FLIP_RTL &&
  508. goog.style.isRightToLeft(element) ?
  509. corner ^ goog.positioning.CornerBit.RIGHT :
  510. corner) &
  511. ~goog.positioning.CornerBit.FLIP_RTL);
  512. };
  513. /**
  514. * Returns the corner opposite the given one horizontally.
  515. * @param {goog.positioning.Corner} corner The popup corner used to flip.
  516. * @return {goog.positioning.Corner} The opposite corner horizontally.
  517. */
  518. goog.positioning.flipCornerHorizontal = function(corner) {
  519. return /** @type {goog.positioning.Corner} */ (
  520. corner ^ goog.positioning.CornerBit.RIGHT);
  521. };
  522. /**
  523. * Returns the corner opposite the given one vertically.
  524. * @param {goog.positioning.Corner} corner The popup corner used to flip.
  525. * @return {goog.positioning.Corner} The opposite corner vertically.
  526. */
  527. goog.positioning.flipCornerVertical = function(corner) {
  528. return /** @type {goog.positioning.Corner} */ (
  529. corner ^ goog.positioning.CornerBit.BOTTOM);
  530. };
  531. /**
  532. * Returns the corner opposite the given one horizontally and vertically.
  533. * @param {goog.positioning.Corner} corner The popup corner used to flip.
  534. * @return {goog.positioning.Corner} The opposite corner horizontally and
  535. * vertically.
  536. */
  537. goog.positioning.flipCorner = function(corner) {
  538. return /** @type {goog.positioning.Corner} */ (
  539. corner ^ goog.positioning.CornerBit.BOTTOM ^
  540. goog.positioning.CornerBit.RIGHT);
  541. };