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.

positioning.ts 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  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. }
  54. export type BasePlacement = Extract<
  55. PopupPlacement,
  56. PopupPlacement.Bottom | PopupPlacement.Top | PopupPlacement.Left | PopupPlacement.Right
  57. >;
  58. export const PLACEMENT_FLIP_MAP: { [key in PopupPlacement]: PopupPlacement } = {
  59. [PopupPlacement.Left]: PopupPlacement.Right,
  60. [PopupPlacement.LeftBottom]: PopupPlacement.RightBottom,
  61. [PopupPlacement.LeftTop]: PopupPlacement.RightTop,
  62. [PopupPlacement.Right]: PopupPlacement.Left,
  63. [PopupPlacement.RightBottom]: PopupPlacement.LeftBottom,
  64. [PopupPlacement.RightTop]: PopupPlacement.LeftTop,
  65. [PopupPlacement.Top]: PopupPlacement.Bottom,
  66. [PopupPlacement.TopLeft]: PopupPlacement.BottomLeft,
  67. [PopupPlacement.TopRight]: PopupPlacement.BottomRight,
  68. [PopupPlacement.Bottom]: PopupPlacement.Top,
  69. [PopupPlacement.BottomLeft]: PopupPlacement.TopLeft,
  70. [PopupPlacement.BottomRight]: PopupPlacement.TopRight,
  71. };
  72. const MARGIN_TO_EDGE = 4;
  73. export function popupPositioning(
  74. toggleNode: Element,
  75. popupNode: Element,
  76. placement: PopupPlacement = PopupPlacement.Bottom
  77. ) {
  78. const toggleRect = toggleNode.getBoundingClientRect();
  79. const popupRect = popupNode.getBoundingClientRect();
  80. let left = 0;
  81. let top = 0;
  82. switch (placement) {
  83. case PopupPlacement.Bottom:
  84. left = toggleRect.left + toggleRect.width / 2 - popupRect.width / 2;
  85. top = toggleRect.top + toggleRect.height;
  86. break;
  87. case PopupPlacement.BottomLeft:
  88. left = toggleRect.left;
  89. top = toggleRect.top + toggleRect.height;
  90. break;
  91. case PopupPlacement.BottomRight:
  92. left = toggleRect.left + toggleRect.width - popupRect.width;
  93. top = toggleRect.top + toggleRect.height;
  94. break;
  95. case PopupPlacement.Left:
  96. left = toggleRect.left - popupRect.width;
  97. top = toggleRect.top + toggleRect.height / 2 - popupRect.height / 2;
  98. break;
  99. case PopupPlacement.LeftTop:
  100. left = toggleRect.left - popupRect.width;
  101. top = toggleRect.top;
  102. break;
  103. case PopupPlacement.LeftBottom:
  104. left = toggleRect.left - popupRect.width;
  105. top = toggleRect.top + toggleRect.height - popupRect.height;
  106. break;
  107. case PopupPlacement.Right:
  108. left = toggleRect.left + toggleRect.width;
  109. top = toggleRect.top + toggleRect.height / 2 - popupRect.height / 2;
  110. break;
  111. case PopupPlacement.RightTop:
  112. left = toggleRect.left + toggleRect.width;
  113. top = toggleRect.top;
  114. break;
  115. case PopupPlacement.RightBottom:
  116. left = toggleRect.left + toggleRect.width;
  117. top = toggleRect.top + toggleRect.height - popupRect.height;
  118. break;
  119. case PopupPlacement.Top:
  120. left = toggleRect.left + toggleRect.width / 2 - popupRect.width / 2;
  121. top = toggleRect.top - popupRect.height;
  122. break;
  123. case PopupPlacement.TopLeft:
  124. left = toggleRect.left;
  125. top = toggleRect.top - popupRect.height;
  126. break;
  127. case PopupPlacement.TopRight:
  128. left = toggleRect.left + toggleRect.width - popupRect.width;
  129. top = toggleRect.top - popupRect.height;
  130. break;
  131. }
  132. const inBoundariesLeft = Math.min(
  133. Math.max(left, getMinLeftPlacement(toggleRect)),
  134. getMaxLeftPlacement(toggleRect, popupRect)
  135. );
  136. const inBoundariesTop = Math.min(
  137. Math.max(top, getMinTopPlacement(toggleRect)),
  138. getMaxTopPlacement(toggleRect, popupRect)
  139. );
  140. return {
  141. height: popupRect.height,
  142. left: inBoundariesLeft,
  143. leftFix: inBoundariesLeft - left,
  144. top: inBoundariesTop,
  145. topFix: inBoundariesTop - top,
  146. width: popupRect.width,
  147. };
  148. }
  149. function getMinLeftPlacement(toggleRect: DOMRect) {
  150. return Math.min(
  151. MARGIN_TO_EDGE, // Left edge of the sceen
  152. toggleRect.left + toggleRect.width / 2 // Left edge of the screen when scrolled
  153. );
  154. }
  155. function getMaxLeftPlacement(toggleRect: DOMRect, popupRect: DOMRect) {
  156. return Math.max(
  157. document.documentElement.clientWidth - popupRect.width - MARGIN_TO_EDGE, // Right edge of the screen
  158. toggleRect.left + toggleRect.width / 2 - popupRect.width // Right edge of the screen when scrolled
  159. );
  160. }
  161. function getMinTopPlacement(toggleRect: DOMRect) {
  162. return Math.min(
  163. MARGIN_TO_EDGE, // Top edge of the sceen
  164. toggleRect.top + toggleRect.height / 2 // Top edge of the screen when scrolled
  165. );
  166. }
  167. function getMaxTopPlacement(toggleRect: DOMRect, popupRect: DOMRect) {
  168. return Math.max(
  169. document.documentElement.clientHeight - popupRect.height - MARGIN_TO_EDGE, // Bottom edge of the screen
  170. toggleRect.top + toggleRect.height / 2 - popupRect.height // Bottom edge of the screen when scrolled
  171. );
  172. }