You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2023 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. /**
  21. * Positioning rules:
  22. * - Bottom = below the block, horizontally centered
  23. * - BottomLeft = below the block, horizontally left-aligned
  24. * - BottomRight = below the block, horizontally right-aligned
  25. * - Left = Left of the block, vertically centered
  26. * - LeftTop = on the left-side of the block, vertically top-aligned
  27. * - LeftBottom = on the left-side of the block, vertically bottom-aligned
  28. * - Right = Right of the block, vertically centered
  29. * - RightTop = on the right-side of the block, vertically top-aligned
  30. * - RightBottom = on the right-side of the block, vetically bottom-aligned
  31. * - Top = above the block, horizontally centered
  32. * - TopLeft = above the block, horizontally left-aligned
  33. * - TopRight = above the block, horizontally right-aligned
  34. */
  35. export enum PopupPlacement {
  36. Bottom = 'bottom',
  37. BottomLeft = 'bottom-left',
  38. BottomRight = 'bottom-right',
  39. Left = 'left',
  40. LeftTop = 'left-top',
  41. LeftBottom = 'left-bottom',
  42. Right = 'right',
  43. RightTop = 'right-top',
  44. RightBottom = 'right-bottom',
  45. Top = 'top',
  46. TopLeft = 'top-left',
  47. TopRight = 'top-right',
  48. }
  49. export enum PopupZLevel {
  50. Content = 'content',
  51. Default = 'popup',
  52. Global = 'global',
  53. Absolute = 'absolute',
  54. }
  55. export type BasePlacement = Extract<
  56. PopupPlacement,
  57. PopupPlacement.Bottom | PopupPlacement.Top | PopupPlacement.Left | PopupPlacement.Right
  58. >;
  59. export const PLACEMENT_FLIP_MAP: { [key in PopupPlacement]: PopupPlacement } = {
  60. [PopupPlacement.Left]: PopupPlacement.Right,
  61. [PopupPlacement.LeftBottom]: PopupPlacement.RightBottom,
  62. [PopupPlacement.LeftTop]: PopupPlacement.RightTop,
  63. [PopupPlacement.Right]: PopupPlacement.Left,
  64. [PopupPlacement.RightBottom]: PopupPlacement.LeftBottom,
  65. [PopupPlacement.RightTop]: PopupPlacement.LeftTop,
  66. [PopupPlacement.Top]: PopupPlacement.Bottom,
  67. [PopupPlacement.TopLeft]: PopupPlacement.BottomLeft,
  68. [PopupPlacement.TopRight]: PopupPlacement.BottomRight,
  69. [PopupPlacement.Bottom]: PopupPlacement.Top,
  70. [PopupPlacement.BottomLeft]: PopupPlacement.TopLeft,
  71. [PopupPlacement.BottomRight]: PopupPlacement.TopRight,
  72. };
  73. const MARGIN_TO_EDGE = 4;
  74. export function popupPositioning(
  75. toggleNode: Element,
  76. popupNode: Element,
  77. placement: PopupPlacement = PopupPlacement.Bottom,
  78. ) {
  79. const toggleRect = toggleNode.getBoundingClientRect();
  80. const popupRect = popupNode.getBoundingClientRect();
  81. let { left, top } = toggleRect;
  82. switch (placement) {
  83. case PopupPlacement.Bottom:
  84. left += toggleRect.width / 2 - popupRect.width / 2;
  85. top += toggleRect.height;
  86. break;
  87. case PopupPlacement.BottomLeft:
  88. top += toggleRect.height;
  89. break;
  90. case PopupPlacement.BottomRight:
  91. left += toggleRect.width - popupRect.width;
  92. top += toggleRect.height;
  93. break;
  94. case PopupPlacement.Left:
  95. left -= popupRect.width;
  96. top += toggleRect.height / 2 - popupRect.height / 2;
  97. break;
  98. case PopupPlacement.LeftTop:
  99. left -= popupRect.width;
  100. break;
  101. case PopupPlacement.LeftBottom:
  102. left -= popupRect.width;
  103. top += toggleRect.height - popupRect.height;
  104. break;
  105. case PopupPlacement.Right:
  106. left += toggleRect.width;
  107. top += toggleRect.height / 2 - popupRect.height / 2;
  108. break;
  109. case PopupPlacement.RightTop:
  110. left += toggleRect.width;
  111. break;
  112. case PopupPlacement.RightBottom:
  113. left += toggleRect.width;
  114. top += toggleRect.height - popupRect.height;
  115. break;
  116. case PopupPlacement.Top:
  117. left += toggleRect.width / 2 - popupRect.width / 2;
  118. top -= popupRect.height;
  119. break;
  120. case PopupPlacement.TopLeft:
  121. top -= popupRect.height;
  122. break;
  123. case PopupPlacement.TopRight:
  124. left += toggleRect.width - popupRect.width;
  125. top -= popupRect.height;
  126. break;
  127. }
  128. const inBoundariesLeft = Math.min(
  129. Math.max(left, getMinLeftPlacement(toggleRect)),
  130. getMaxLeftPlacement(toggleRect, popupRect),
  131. );
  132. const inBoundariesTop = Math.min(
  133. Math.max(top, getMinTopPlacement(toggleRect)),
  134. getMaxTopPlacement(toggleRect, popupRect),
  135. );
  136. return {
  137. height: popupRect.height,
  138. left: inBoundariesLeft,
  139. leftFix: inBoundariesLeft - left,
  140. top: inBoundariesTop,
  141. topFix: inBoundariesTop - top,
  142. width: popupRect.width,
  143. };
  144. }
  145. function getMinLeftPlacement(toggleRect: DOMRect) {
  146. return Math.min(
  147. MARGIN_TO_EDGE, // Left edge of the sceen
  148. toggleRect.left + toggleRect.width / 2, // Left edge of the screen when scrolled
  149. );
  150. }
  151. function getMaxLeftPlacement(toggleRect: DOMRect, popupRect: DOMRect) {
  152. return Math.max(
  153. document.documentElement.clientWidth - popupRect.width - MARGIN_TO_EDGE, // Right edge of the screen
  154. toggleRect.left + toggleRect.width / 2 - popupRect.width, // Right edge of the screen when scrolled
  155. );
  156. }
  157. function getMinTopPlacement(toggleRect: DOMRect) {
  158. return Math.min(
  159. MARGIN_TO_EDGE, // Top edge of the sceen
  160. toggleRect.top + toggleRect.height / 2, // Top edge of the screen when scrolled
  161. );
  162. }
  163. function getMaxTopPlacement(toggleRect: DOMRect, popupRect: DOMRect) {
  164. return Math.max(
  165. document.documentElement.clientHeight - popupRect.height - MARGIN_TO_EDGE, // Bottom edge of the screen
  166. toggleRect.top + toggleRect.height / 2 - popupRect.height, // Bottom edge of the screen when scrolled
  167. );
  168. }