bidi.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. // Copyright 2012 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 Bidi utility functions.
  16. *
  17. */
  18. goog.provide('goog.style.bidi');
  19. goog.require('goog.dom');
  20. goog.require('goog.style');
  21. goog.require('goog.userAgent');
  22. goog.require('goog.userAgent.product');
  23. goog.require('goog.userAgent.product.isVersion');
  24. /**
  25. * Returns the normalized scrollLeft position for a scrolled element.
  26. * @param {Element} element The scrolled element.
  27. * @return {number} The number of pixels the element is scrolled. 0 indicates
  28. * that the element is not scrolled at all (which, in general, is the
  29. * left-most position in ltr and the right-most position in rtl).
  30. */
  31. goog.style.bidi.getScrollLeft = function(element) {
  32. var isRtl = goog.style.isRightToLeft(element);
  33. var isSafari10Plus =
  34. goog.userAgent.product.SAFARI && goog.userAgent.product.isVersion(10);
  35. if (isRtl && (goog.userAgent.GECKO || isSafari10Plus)) {
  36. // ScrollLeft starts at 0 and then goes negative as the element is scrolled
  37. // towards the left.
  38. return -element.scrollLeft;
  39. } else if (
  40. isRtl &&
  41. !(goog.userAgent.EDGE_OR_IE && goog.userAgent.isVersionOrHigher('8'))) {
  42. // ScrollLeft starts at the maximum positive value and decreases towards
  43. // 0 as the element is scrolled towards the left. However, for overflow
  44. // visible, there is no scrollLeft and the value always stays correctly at 0
  45. var overflowX = goog.style.getComputedOverflowX(element);
  46. if (overflowX == 'visible') {
  47. return element.scrollLeft;
  48. } else {
  49. return element.scrollWidth - element.clientWidth - element.scrollLeft;
  50. }
  51. }
  52. // ScrollLeft behavior is identical in rtl and ltr, it starts at 0 and
  53. // increases as the element is scrolled away from the start.
  54. return element.scrollLeft;
  55. };
  56. /**
  57. * Returns the "offsetStart" of an element, analogous to offsetLeft but
  58. * normalized for right-to-left environments and various browser
  59. * inconsistencies. This value returned can always be passed to setScrollOffset
  60. * to scroll to an element's left edge in a left-to-right offsetParent or
  61. * right edge in a right-to-left offsetParent.
  62. *
  63. * For example, here offsetStart is 10px in an LTR environment and 5px in RTL:
  64. *
  65. * <pre>
  66. * | xxxxxxxxxx |
  67. * ^^^^^^^^^^ ^^^^ ^^^^^
  68. * 10px elem 5px
  69. * </pre>
  70. *
  71. * If an element is positioned before the start of its offsetParent, the
  72. * startOffset may be negative. This can be used with setScrollOffset to
  73. * reliably scroll to an element:
  74. *
  75. * <pre>
  76. * var scrollOffset = goog.style.bidi.getOffsetStart(element);
  77. * goog.style.bidi.setScrollOffset(element.offsetParent, scrollOffset);
  78. * </pre>
  79. *
  80. * @see setScrollOffset
  81. *
  82. * @param {Element} element The element for which we need to determine the
  83. * offsetStart position.
  84. * @return {number} The offsetStart for that element.
  85. */
  86. goog.style.bidi.getOffsetStart = function(element) {
  87. element = /** @type {!HTMLElement} */ (element);
  88. var offsetLeftForReal = element.offsetLeft;
  89. // The element might not have an offsetParent.
  90. // For example, the node might not be attached to the DOM tree,
  91. // and position:fixed children do not have an offset parent.
  92. // Just try to do the best we can with what we have.
  93. var bestParent = element.offsetParent;
  94. if (!bestParent && goog.style.getComputedPosition(element) == 'fixed') {
  95. bestParent = goog.dom.getOwnerDocument(element).documentElement;
  96. }
  97. // Just give up in this case.
  98. if (!bestParent) {
  99. return offsetLeftForReal;
  100. }
  101. if (goog.userAgent.GECKO) {
  102. // When calculating an element's offsetLeft, Firefox erroneously subtracts
  103. // the border width from the actual distance. So we need to add it back.
  104. var borderWidths = goog.style.getBorderBox(bestParent);
  105. offsetLeftForReal += borderWidths.left;
  106. } else if (
  107. goog.userAgent.isDocumentModeOrHigher(8) &&
  108. !goog.userAgent.isDocumentModeOrHigher(9)) {
  109. // When calculating an element's offsetLeft, IE8/9-Standards Mode
  110. // erroneously adds the border width to the actual distance. So we need to
  111. // subtract it.
  112. var borderWidths = goog.style.getBorderBox(bestParent);
  113. offsetLeftForReal -= borderWidths.left;
  114. }
  115. if (goog.style.isRightToLeft(bestParent)) {
  116. // Right edge of the element relative to the left edge of its parent.
  117. var elementRightOffset = offsetLeftForReal + element.offsetWidth;
  118. // Distance from the parent's right edge to the element's right edge.
  119. return bestParent.clientWidth - elementRightOffset;
  120. }
  121. return offsetLeftForReal;
  122. };
  123. /**
  124. * Sets the element's scrollLeft attribute so it is correctly scrolled by
  125. * offsetStart pixels. This takes into account whether the element is RTL and
  126. * the nuances of different browsers. To scroll to the "beginning" of an
  127. * element use getOffsetStart to obtain the element's offsetStart value and then
  128. * pass the value to setScrollOffset.
  129. * @see getOffsetStart
  130. * @param {Element} element The element to set scrollLeft on.
  131. * @param {number} offsetStart The number of pixels to scroll the element.
  132. * If this value is < 0, 0 is used.
  133. */
  134. goog.style.bidi.setScrollOffset = function(element, offsetStart) {
  135. offsetStart = Math.max(offsetStart, 0);
  136. // In LTR and in "mirrored" browser RTL (such as IE), we set scrollLeft to
  137. // the number of pixels to scroll.
  138. // Otherwise, in RTL, we need to account for different browser behavior.
  139. if (!goog.style.isRightToLeft(element)) {
  140. element.scrollLeft = offsetStart;
  141. } else if (goog.userAgent.GECKO) {
  142. // Negative scroll-left positions in RTL.
  143. element.scrollLeft = -offsetStart;
  144. } else if (
  145. !(goog.userAgent.EDGE_OR_IE && goog.userAgent.isVersionOrHigher('8'))) {
  146. // Take the current scrollLeft value and move to the right by the
  147. // offsetStart to get to the left edge of the element, and then by
  148. // the clientWidth of the element to get to the right edge.
  149. element.scrollLeft =
  150. element.scrollWidth - offsetStart - element.clientWidth;
  151. } else {
  152. element.scrollLeft = offsetStart;
  153. }
  154. };
  155. /**
  156. * Sets the element's left style attribute in LTR or right style attribute in
  157. * RTL. Also clears the left attribute in RTL and the right attribute in LTR.
  158. * @param {Element} elem The element to position.
  159. * @param {number} left The left position in LTR; will be set as right in RTL.
  160. * @param {?number} top The top position. If null only the left/right is set.
  161. * @param {boolean} isRtl Whether we are in RTL mode.
  162. */
  163. goog.style.bidi.setPosition = function(elem, left, top, isRtl) {
  164. if (!goog.isNull(top)) {
  165. elem.style.top = top + 'px';
  166. }
  167. if (isRtl) {
  168. elem.style.right = left + 'px';
  169. elem.style.left = '';
  170. } else {
  171. elem.style.left = left + 'px';
  172. elem.style.right = '';
  173. }
  174. };