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.

109 lines
3.1 KiB

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