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.

120 lines
3.6 KiB

2 months ago
  1. // @flow
  2. import type { Modifier, ModifierArguments, Padding, Rect } from '../types';
  3. import type { Placement } from '../enums';
  4. import getBasePlacement from '../utils/getBasePlacement';
  5. import getLayoutRect from '../dom-utils/getLayoutRect';
  6. import contains from '../dom-utils/contains';
  7. import getOffsetParent from '../dom-utils/getOffsetParent';
  8. import getMainAxisFromPlacement from '../utils/getMainAxisFromPlacement';
  9. import { within } from '../utils/within';
  10. import mergePaddingObject from '../utils/mergePaddingObject';
  11. import expandToHashMap from '../utils/expandToHashMap';
  12. import { left, right, basePlacements, top, bottom } from '../enums';
  13. // eslint-disable-next-line import/no-unused-modules
  14. export type Options = {
  15. element: HTMLElement | string | null,
  16. padding:
  17. | Padding
  18. | (({|
  19. popper: Rect,
  20. reference: Rect,
  21. placement: Placement,
  22. |}) => Padding),
  23. };
  24. const toPaddingObject = (padding, state) => {
  25. padding =
  26. typeof padding === 'function'
  27. ? padding({ ...state.rects, placement: state.placement })
  28. : padding;
  29. return mergePaddingObject(
  30. typeof padding !== 'number'
  31. ? padding
  32. : expandToHashMap(padding, basePlacements)
  33. );
  34. };
  35. function arrow({ state, name, options }: ModifierArguments<Options>) {
  36. const arrowElement = state.elements.arrow;
  37. const popperOffsets = state.modifiersData.popperOffsets;
  38. const basePlacement = getBasePlacement(state.placement);
  39. const axis = getMainAxisFromPlacement(basePlacement);
  40. const isVertical = [left, right].indexOf(basePlacement) >= 0;
  41. const len = isVertical ? 'height' : 'width';
  42. if (!arrowElement || !popperOffsets) {
  43. return;
  44. }
  45. const paddingObject = toPaddingObject(options.padding, state);
  46. const arrowRect = getLayoutRect(arrowElement);
  47. const minProp = axis === 'y' ? top : left;
  48. const maxProp = axis === 'y' ? bottom : right;
  49. const endDiff =
  50. state.rects.reference[len] +
  51. state.rects.reference[axis] -
  52. popperOffsets[axis] -
  53. state.rects.popper[len];
  54. const startDiff = popperOffsets[axis] - state.rects.reference[axis];
  55. const arrowOffsetParent = getOffsetParent(arrowElement);
  56. const clientSize = arrowOffsetParent
  57. ? axis === 'y'
  58. ? arrowOffsetParent.clientHeight || 0
  59. : arrowOffsetParent.clientWidth || 0
  60. : 0;
  61. const centerToReference = endDiff / 2 - startDiff / 2;
  62. // Make sure the arrow doesn't overflow the popper if the center point is
  63. // outside of the popper bounds
  64. const min = paddingObject[minProp];
  65. const max = clientSize - arrowRect[len] - paddingObject[maxProp];
  66. const center = clientSize / 2 - arrowRect[len] / 2 + centerToReference;
  67. const offset = within(min, center, max);
  68. // Prevents breaking syntax highlighting...
  69. const axisProp: string = axis;
  70. state.modifiersData[name] = {
  71. [axisProp]: offset,
  72. centerOffset: offset - center,
  73. };
  74. }
  75. function effect({ state, options }: ModifierArguments<Options>) {
  76. let { element: arrowElement = '[data-popper-arrow]' } = options;
  77. if (arrowElement == null) {
  78. return;
  79. }
  80. // CSS selector
  81. if (typeof arrowElement === 'string') {
  82. arrowElement = state.elements.popper.querySelector(arrowElement);
  83. if (!arrowElement) {
  84. return;
  85. }
  86. }
  87. if (!contains(state.elements.popper, arrowElement)) {
  88. return;
  89. }
  90. state.elements.arrow = arrowElement;
  91. }
  92. // eslint-disable-next-line import/no-unused-modules
  93. export type ArrowModifier = Modifier<'arrow', Options>;
  94. export default ({
  95. name: 'arrow',
  96. enabled: true,
  97. phase: 'main',
  98. fn: arrow,
  99. effect,
  100. requires: ['popperOffsets'],
  101. requiresIfExists: ['preventOverflow'],
  102. }: ArrowModifier);