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.

112 lines
3.2 KiB

2 months ago
  1. // @flow
  2. import type { State, SideObject, Padding, PositioningStrategy } from '../types';
  3. import type { Placement, Boundary, RootBoundary, Context } from '../enums';
  4. import getClippingRect from '../dom-utils/getClippingRect';
  5. import getDocumentElement from '../dom-utils/getDocumentElement';
  6. import getBoundingClientRect from '../dom-utils/getBoundingClientRect';
  7. import computeOffsets from './computeOffsets';
  8. import rectToClientRect from './rectToClientRect';
  9. import {
  10. clippingParents,
  11. reference,
  12. popper,
  13. bottom,
  14. top,
  15. right,
  16. basePlacements,
  17. viewport,
  18. } from '../enums';
  19. import { isElement } from '../dom-utils/instanceOf';
  20. import mergePaddingObject from './mergePaddingObject';
  21. import expandToHashMap from './expandToHashMap';
  22. // eslint-disable-next-line import/no-unused-modules
  23. export type Options = {
  24. placement: Placement,
  25. strategy: PositioningStrategy,
  26. boundary: Boundary,
  27. rootBoundary: RootBoundary,
  28. elementContext: Context,
  29. altBoundary: boolean,
  30. padding: Padding,
  31. };
  32. export default function detectOverflow(
  33. state: State,
  34. options: $Shape<Options> = {}
  35. ): SideObject {
  36. const {
  37. placement = state.placement,
  38. strategy = state.strategy,
  39. boundary = clippingParents,
  40. rootBoundary = viewport,
  41. elementContext = popper,
  42. altBoundary = false,
  43. padding = 0,
  44. } = options;
  45. const paddingObject = mergePaddingObject(
  46. typeof padding !== 'number'
  47. ? padding
  48. : expandToHashMap(padding, basePlacements)
  49. );
  50. const altContext = elementContext === popper ? reference : popper;
  51. const popperRect = state.rects.popper;
  52. const element = state.elements[altBoundary ? altContext : elementContext];
  53. const clippingClientRect = getClippingRect(
  54. isElement(element)
  55. ? element
  56. : element.contextElement || getDocumentElement(state.elements.popper),
  57. boundary,
  58. rootBoundary,
  59. strategy
  60. );
  61. const referenceClientRect = getBoundingClientRect(state.elements.reference);
  62. const popperOffsets = computeOffsets({
  63. reference: referenceClientRect,
  64. element: popperRect,
  65. strategy: 'absolute',
  66. placement,
  67. });
  68. const popperClientRect = rectToClientRect({
  69. ...popperRect,
  70. ...popperOffsets,
  71. });
  72. const elementClientRect =
  73. elementContext === popper ? popperClientRect : referenceClientRect;
  74. // positive = overflowing the clipping rect
  75. // 0 or negative = within the clipping rect
  76. const overflowOffsets = {
  77. top: clippingClientRect.top - elementClientRect.top + paddingObject.top,
  78. bottom:
  79. elementClientRect.bottom -
  80. clippingClientRect.bottom +
  81. paddingObject.bottom,
  82. left: clippingClientRect.left - elementClientRect.left + paddingObject.left,
  83. right:
  84. elementClientRect.right - clippingClientRect.right + paddingObject.right,
  85. };
  86. const offsetData = state.modifiersData.offset;
  87. // Offsets can be applied only to the popper element
  88. if (elementContext === popper && offsetData) {
  89. const offset = offsetData[placement];
  90. Object.keys(overflowOffsets).forEach((key) => {
  91. const multiply = [right, bottom].indexOf(key) >= 0 ? 1 : -1;
  92. const axis = [top, bottom].indexOf(key) >= 0 ? 'y' : 'x';
  93. overflowOffsets[key] += offset[axis] * multiply;
  94. });
  95. }
  96. return overflowOffsets;
  97. }