orient.js

  1. /**
  2. * @module orient
  3. * @desc The orient module provides functions to determine the optimum orientation of a tooltip
  4. * if auto-sizing is set to true, and to set the proper CSS values for the tooltip's div elements.
  5. */
  6. import {ttDiv, beforeRule, afterRule} from './init.js';
  7. import {windowHeight, windowWidth, getElementCoordinates, overlap, parseSize} from './utils.js';
  8. /**
  9. * @function position
  10. * @desc Sets the position of the tooltip relative to the HTML element with which it is associated.
  11. * @param {string} targetElement The unique id of the HTML element that owns the tooltip.
  12. * @param {Tip} target The object containing all of the current tooltip's options and content.
  13. */
  14. export function position (targetElement, target, orientation) {
  15. const targetCoordinates = getElementCoordinates(targetElement);
  16. const divWidth = ttDiv.getBoundingClientRect()['width'];
  17. const divHeight = ttDiv.getBoundingClientRect()['height'];
  18. const halfDivHeight = divHeight / 2;
  19. const halfDivWidth = divWidth / 2;
  20. const borderRadius = parseSize(target.borderRadius(), 'width', ttDiv);
  21. const arrowSize = parseSize(target.arrowSize(), 'width', ttDiv);
  22. afterRule.borderWidth = arrowSize + 'px';
  23. let top;
  24. let left;
  25. let verticalAdjust;
  26. let horizontalAdjust;
  27. let sizeAdjust;
  28. let adjustVertical = function (top) {
  29. let topAdjust = top;
  30. let arrowAdjust = halfDivHeight;
  31. if (top < 0) {
  32. topAdjust = 0;
  33. arrowAdjust = Math.max(arrowSize + borderRadius, top + halfDivHeight);
  34. } else if (top + divHeight > windowHeight) {
  35. topAdjust = windowHeight - divHeight;
  36. arrowAdjust = Math.min(divHeight - borderRadius - arrowSize, halfDivHeight + top - topAdjust);
  37. };
  38. return {topAdjust: Math.round(topAdjust), arrowAdjust: Math.round(arrowAdjust)};
  39. }
  40. let adjustHorizontal = function (left) {
  41. let leftAdjust = left;
  42. let arrowAdjust = halfDivWidth;
  43. if (left < 0) {
  44. leftAdjust = 0;
  45. arrowAdjust = Math.max(arrowSize + borderRadius, left + halfDivWidth);
  46. } else if (left + divWidth > windowWidth) {
  47. leftAdjust = windowWidth - divWidth;
  48. arrowAdjust = Math.min(divWidth - borderRadius - arrowSize, halfDivWidth + left - leftAdjust);
  49. };
  50. return {leftAdjust: Math.round(leftAdjust), arrowAdjust: Math.round(arrowAdjust)};
  51. }
  52. switch (orientation) {
  53. case 'top': {
  54. top = targetCoordinates.top - arrowSize - divHeight;
  55. if (top < 0) {
  56. beforeRule.height = Math.round(divHeight + top) + 'px';
  57. top = 0;
  58. };
  59. left = (targetCoordinates.width / 2) + targetCoordinates.left - halfDivWidth;
  60. horizontalAdjust = adjustHorizontal(left);
  61. beforeRule.top = Math.round(top) + 'px';
  62. beforeRule.left = horizontalAdjust.leftAdjust + 'px';
  63. afterRule.top = '99.5%'; // '100%';
  64. afterRule.left = horizontalAdjust.arrowAdjust + 'px';
  65. afterRule.bottom = '';
  66. afterRule.right = '';
  67. afterRule.marginLeft = -arrowSize + 'px';
  68. afterRule.marginTop = '';
  69. afterRule.borderColor = target.backgroundColor() + ' transparent transparent transparent';
  70. break;
  71. };
  72. case 'bottom': {
  73. top = targetCoordinates.top + targetCoordinates.height + arrowSize;
  74. sizeAdjust = windowHeight - divHeight + top + arrowSize;
  75. beforeRule.height = (sizeAdjust < 0) ? (divHeight + sizeAdjust) + 'px' : beforeRule.height;
  76. left = (targetCoordinates.width / 2) + targetCoordinates.left - halfDivWidth;
  77. horizontalAdjust = adjustHorizontal(left);
  78. beforeRule.top = Math.round(top) + 'px';
  79. beforeRule.left = horizontalAdjust.leftAdjust + 'px';
  80. afterRule.top = '';
  81. afterRule.left = horizontalAdjust.arrowAdjust + 'px';
  82. afterRule.bottom = '99.5%'; //'100%';
  83. afterRule.right = '';
  84. afterRule.marginLeft = -arrowSize + 'px';
  85. afterRule.marginTop = '';
  86. afterRule.borderColor = 'transparent transparent ' + target.backgroundColor() + ' transparent';
  87. break;
  88. };
  89. case 'left': {
  90. top = (targetCoordinates.height / 2) + targetCoordinates.top - halfDivHeight;
  91. left = targetCoordinates.left - divWidth - arrowSize;
  92. if (left < 0) {
  93. beforeRule.width = Math.round(divWidth + left) + 'px';
  94. left = 0;
  95. };
  96. verticalAdjust = adjustVertical(top);
  97. beforeRule.top = verticalAdjust.topAdjust + 'px';
  98. beforeRule.left = Math.round(left) + 'px';
  99. afterRule.top = verticalAdjust.arrowAdjust + 'px';
  100. afterRule.left = '99.5%'; //'100%';
  101. afterRule.bottom = '';
  102. afterRule.right = '';
  103. afterRule.marginLeft = '';
  104. afterRule.marginTop = -arrowSize + 'px';
  105. afterRule.borderColor = 'transparent transparent transparent ' + target.backgroundColor();
  106. break;
  107. };
  108. case 'right': {
  109. top = (targetCoordinates.height / 2) + targetCoordinates.top - halfDivHeight;
  110. left = targetCoordinates.left + targetCoordinates.width + arrowSize;
  111. sizeAdjust = windowWidth - divWidth + left + arrowSize;
  112. beforeRule.width = (sizeAdjust < 0) ? (divWidth + sizeAdjust) + 'px' : beforeRule.width;
  113. verticalAdjust = adjustVertical(top);
  114. beforeRule.top = verticalAdjust.topAdjust + 'px';
  115. beforeRule.left = Math.round(left) + 'px';
  116. afterRule.top = verticalAdjust.arrowAdjust + 'px';
  117. afterRule.left = '';
  118. afterRule.bottom = '';
  119. afterRule.right = '99.5%'; //'100%';
  120. afterRule.marginLeft = '';
  121. afterRule.marginTop = -arrowSize + 'px';
  122. afterRule.borderColor = 'transparent ' + target.backgroundColor() + ' transparent transparent';
  123. break;
  124. };
  125. };
  126. }
  127. /**
  128. * @function optimumOrientation
  129. * @desc Determines the best side upon which to place the tooltip.
  130. * If [auto-positioning]{@link Tip#autoPosition} is on, the
  131. * [preferred-orientation]{@link Tip#prefferedOrientation}
  132. * setting will be honored unless there is insufficient viewport space.
  133. * in this case, the position with the most available space will be used.
  134. * @param {string} targetElement The DOM element that is associated with the tooltip.
  135. * @param {Tip} target The [Tip]{@link Tip} class object containing all of the current tooltip's options and content.
  136. * @returns {string} One of ['left' | 'right' | 'top' | 'bottom'].
  137. * @since v2.2.0
  138. */
  139. export function optimumOrientation (targetElement, target) {
  140. const elementCoordinates = getElementCoordinates(targetElement);
  141. const arrowSize = parseSize(target.arrowSize(), 'width', ttDiv);
  142. const midX = elementCoordinates.left + (elementCoordinates.width / 2);
  143. const midY = elementCoordinates.top + (elementCoordinates.height / 2);
  144. const divWidth = ttDiv.getBoundingClientRect()['width'];
  145. const divHeight = ttDiv.getBoundingClientRect()['height'];
  146. const halfDivHeight = divHeight / 2;
  147. const halfDivWidth = divWidth / 2;
  148. const leftOverlap = overlap ('left',
  149. {
  150. x0: elementCoordinates.left - arrowSize - divWidth,
  151. x1: elementCoordinates.left - arrowSize,
  152. y0: midY - halfDivHeight,
  153. y1: midY + halfDivHeight
  154. });
  155. const rightOverlap = overlap ('right',
  156. {
  157. x0: elementCoordinates.left + elementCoordinates.width + arrowSize,
  158. x1: elementCoordinates.left + elementCoordinates.width + arrowSize + divWidth,
  159. y0: midY - halfDivHeight,
  160. y1: midY + halfDivHeight
  161. });
  162. const topOverlap = overlap ('top',
  163. {
  164. x0: midX - halfDivWidth,
  165. x1: midX + halfDivWidth,
  166. y0: elementCoordinates.top - arrowSize - divHeight,
  167. y1: elementCoordinates.top - arrowSize
  168. });
  169. const bottomOverlap = overlap ('bottom',
  170. {
  171. x0: midX - halfDivWidth,
  172. x1: midX + halfDivWidth,
  173. y0: elementCoordinates.top + elementCoordinates.height + arrowSize,
  174. y1: elementCoordinates.top + elementCoordinates.height + arrowSize + divHeight
  175. });
  176. switch (target.preferredOrientation()) {
  177. case 'left': { if (leftOverlap.overlap == 1) return 'left'; break; };
  178. case 'right': { if (rightOverlap.overlap == 1) return 'right'; break; };
  179. case 'top': { if (topOverlap.overlap == 1) return 'top'; break; };
  180. case 'bottom': { if (bottomOverlap.overlap == 1) return 'bottom'; break; };
  181. };
  182. // if there is no preferred orientation or all overlaps are less than 1
  183. let overlaps = [leftOverlap, rightOverlap, topOverlap, bottomOverlap];
  184. //if all of the overlaps are less than 1 return the greatest
  185. if (leftOverlap.overlap < 1 && rightOverlap.overlap < 1 && topOverlap.overlap < 1 && bottomOverlap.overlap < 1) {
  186. return overlaps[overlaps.reduce((prev, current, index, array) => {
  187. if (current.overlap > array[prev].overlap) {return index} else {return prev};
  188. }, 0)].side;
  189. };
  190. // remove all overlaps that are less than 1
  191. overlaps = overlaps.reduce((prev, current) => {
  192. if (current.overlap == 1) {return prev.concat(current)} else {return prev};
  193. }, []);
  194. if (overlaps.length == 1) { return overlaps[0].side; }; // only one left;
  195. return overlaps[overlaps.reduce((prev, current, index, array) => {
  196. if (current.spacing[current.side] >= array[prev].spacing[prev.side]) {return index} else { return prev} ;
  197. }, 0)].side;
  198. }
  199. /**
  200. * @function getOrientation
  201. * @desc Selects the appropriate orientation by deconflicting [auto-positioning]{@link Tip#autoPosition},
  202. * [prefererd-orientation]{@link Tip#preferredOrientation, and [orientation]{@link Tip#orientation}
  203. * settings in the Tip{} class object.
  204. * @param {string} targetElement The DOM element that is associated with the tooltip.
  205. * @param {Tip} target The [Tip]{@link Tip} class object containing all of the current tooltip's options and content.
  206. * @returns {string} One of ['left' | 'right' | 'top' | 'bottom'].
  207. * @since v2.1.2
  208. */
  209. export function getOrientation (targetElement, target) {
  210. if (target.orientation() != undefined) return target.orientation();
  211. if (target.autoPosition()) return optimumOrientation (targetElement, target);
  212. if (target.preferredOrientation() != 'none') return target.preferredOrientation();
  213. return 'right';
  214. }