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.

106 lines
3.7 KiB

2 months ago
  1. // @flow
  2. import type { ClientRectObject, PositioningStrategy } from '../types';
  3. import type { Boundary, RootBoundary } from '../enums';
  4. import { viewport } from '../enums';
  5. import getViewportRect from './getViewportRect';
  6. import getDocumentRect from './getDocumentRect';
  7. import listScrollParents from './listScrollParents';
  8. import getOffsetParent from './getOffsetParent';
  9. import getDocumentElement from './getDocumentElement';
  10. import getComputedStyle from './getComputedStyle';
  11. import { isElement, isHTMLElement } from './instanceOf';
  12. import getBoundingClientRect from './getBoundingClientRect';
  13. import getParentNode from './getParentNode';
  14. import contains from './contains';
  15. import getNodeName from './getNodeName';
  16. import rectToClientRect from '../utils/rectToClientRect';
  17. import { max, min } from '../utils/math';
  18. function getInnerBoundingClientRect(
  19. element: Element,
  20. strategy: PositioningStrategy
  21. ) {
  22. const rect = getBoundingClientRect(element, false, strategy === 'fixed');
  23. rect.top = rect.top + element.clientTop;
  24. rect.left = rect.left + element.clientLeft;
  25. rect.bottom = rect.top + element.clientHeight;
  26. rect.right = rect.left + element.clientWidth;
  27. rect.width = element.clientWidth;
  28. rect.height = element.clientHeight;
  29. rect.x = rect.left;
  30. rect.y = rect.top;
  31. return rect;
  32. }
  33. function getClientRectFromMixedType(
  34. element: Element,
  35. clippingParent: Element | RootBoundary,
  36. strategy: PositioningStrategy
  37. ): ClientRectObject {
  38. return clippingParent === viewport
  39. ? rectToClientRect(getViewportRect(element, strategy))
  40. : isElement(clippingParent)
  41. ? getInnerBoundingClientRect(clippingParent, strategy)
  42. : rectToClientRect(getDocumentRect(getDocumentElement(element)));
  43. }
  44. // A "clipping parent" is an overflowable container with the characteristic of
  45. // clipping (or hiding) overflowing elements with a position different from
  46. // `initial`
  47. function getClippingParents(element: Element): Array<Element> {
  48. const clippingParents = listScrollParents(getParentNode(element));
  49. const canEscapeClipping =
  50. ['absolute', 'fixed'].indexOf(getComputedStyle(element).position) >= 0;
  51. const clipperElement =
  52. canEscapeClipping && isHTMLElement(element)
  53. ? getOffsetParent(element)
  54. : element;
  55. if (!isElement(clipperElement)) {
  56. return [];
  57. }
  58. // $FlowFixMe[incompatible-return]: https://github.com/facebook/flow/issues/1414
  59. return clippingParents.filter(
  60. (clippingParent) =>
  61. isElement(clippingParent) &&
  62. contains(clippingParent, clipperElement) &&
  63. getNodeName(clippingParent) !== 'body'
  64. );
  65. }
  66. // Gets the maximum area that the element is visible in due to any number of
  67. // clipping parents
  68. export default function getClippingRect(
  69. element: Element,
  70. boundary: Boundary,
  71. rootBoundary: RootBoundary,
  72. strategy: PositioningStrategy
  73. ): ClientRectObject {
  74. const mainClippingParents =
  75. boundary === 'clippingParents'
  76. ? getClippingParents(element)
  77. : [].concat(boundary);
  78. const clippingParents = [...mainClippingParents, rootBoundary];
  79. const firstClippingParent = clippingParents[0];
  80. const clippingRect = clippingParents.reduce((accRect, clippingParent) => {
  81. const rect = getClientRectFromMixedType(element, clippingParent, strategy);
  82. accRect.top = max(rect.top, accRect.top);
  83. accRect.right = min(rect.right, accRect.right);
  84. accRect.bottom = min(rect.bottom, accRect.bottom);
  85. accRect.left = max(rect.left, accRect.left);
  86. return accRect;
  87. }, getClientRectFromMixedType(element, firstClippingParent, strategy));
  88. clippingRect.width = clippingRect.right - clippingRect.left;
  89. clippingRect.height = clippingRect.bottom - clippingRect.top;
  90. clippingRect.x = clippingRect.left;
  91. clippingRect.y = clippingRect.top;
  92. return clippingRect;
  93. }