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.

177 lines
4.8 KiB

2 months ago
  1. // @flow
  2. import type { Placement, Boundary, RootBoundary } from '../enums';
  3. import type { ModifierArguments, Modifier, Padding } from '../types';
  4. import getOppositePlacement from '../utils/getOppositePlacement';
  5. import getBasePlacement from '../utils/getBasePlacement';
  6. import getOppositeVariationPlacement from '../utils/getOppositeVariationPlacement';
  7. import detectOverflow from '../utils/detectOverflow';
  8. import computeAutoPlacement from '../utils/computeAutoPlacement';
  9. import { bottom, top, start, right, left, auto } from '../enums';
  10. import getVariation from '../utils/getVariation';
  11. // eslint-disable-next-line import/no-unused-modules
  12. export type Options = {
  13. mainAxis: boolean,
  14. altAxis: boolean,
  15. fallbackPlacements: Array<Placement>,
  16. padding: Padding,
  17. boundary: Boundary,
  18. rootBoundary: RootBoundary,
  19. altBoundary: boolean,
  20. flipVariations: boolean,
  21. allowedAutoPlacements: Array<Placement>,
  22. };
  23. function getExpandedFallbackPlacements(placement: Placement): Array<Placement> {
  24. if (getBasePlacement(placement) === auto) {
  25. return [];
  26. }
  27. const oppositePlacement = getOppositePlacement(placement);
  28. return [
  29. getOppositeVariationPlacement(placement),
  30. oppositePlacement,
  31. getOppositeVariationPlacement(oppositePlacement),
  32. ];
  33. }
  34. function flip({ state, options, name }: ModifierArguments<Options>) {
  35. if (state.modifiersData[name]._skip) {
  36. return;
  37. }
  38. const {
  39. mainAxis: checkMainAxis = true,
  40. altAxis: checkAltAxis = true,
  41. fallbackPlacements: specifiedFallbackPlacements,
  42. padding,
  43. boundary,
  44. rootBoundary,
  45. altBoundary,
  46. flipVariations = true,
  47. allowedAutoPlacements,
  48. } = options;
  49. const preferredPlacement = state.options.placement;
  50. const basePlacement = getBasePlacement(preferredPlacement);
  51. const isBasePlacement = basePlacement === preferredPlacement;
  52. const fallbackPlacements =
  53. specifiedFallbackPlacements ||
  54. (isBasePlacement || !flipVariations
  55. ? [getOppositePlacement(preferredPlacement)]
  56. : getExpandedFallbackPlacements(preferredPlacement));
  57. const placements = [preferredPlacement, ...fallbackPlacements].reduce(
  58. (acc, placement) => {
  59. return acc.concat(
  60. getBasePlacement(placement) === auto
  61. ? computeAutoPlacement(state, {
  62. placement,
  63. boundary,
  64. rootBoundary,
  65. padding,
  66. flipVariations,
  67. allowedAutoPlacements,
  68. })
  69. : placement
  70. );
  71. },
  72. []
  73. );
  74. const referenceRect = state.rects.reference;
  75. const popperRect = state.rects.popper;
  76. const checksMap = new Map();
  77. let makeFallbackChecks = true;
  78. let firstFittingPlacement = placements[0];
  79. for (let i = 0; i < placements.length; i++) {
  80. const placement = placements[i];
  81. const basePlacement = getBasePlacement(placement);
  82. const isStartVariation = getVariation(placement) === start;
  83. const isVertical = [top, bottom].indexOf(basePlacement) >= 0;
  84. const len = isVertical ? 'width' : 'height';
  85. const overflow = detectOverflow(state, {
  86. placement,
  87. boundary,
  88. rootBoundary,
  89. altBoundary,
  90. padding,
  91. });
  92. let mainVariationSide: any = isVertical
  93. ? isStartVariation
  94. ? right
  95. : left
  96. : isStartVariation
  97. ? bottom
  98. : top;
  99. if (referenceRect[len] > popperRect[len]) {
  100. mainVariationSide = getOppositePlacement(mainVariationSide);
  101. }
  102. const altVariationSide: any = getOppositePlacement(mainVariationSide);
  103. const checks = [];
  104. if (checkMainAxis) {
  105. checks.push(overflow[basePlacement] <= 0);
  106. }
  107. if (checkAltAxis) {
  108. checks.push(
  109. overflow[mainVariationSide] <= 0,
  110. overflow[altVariationSide] <= 0
  111. );
  112. }
  113. if (checks.every((check) => check)) {
  114. firstFittingPlacement = placement;
  115. makeFallbackChecks = false;
  116. break;
  117. }
  118. checksMap.set(placement, checks);
  119. }
  120. if (makeFallbackChecks) {
  121. // `2` may be desired in some cases – research later
  122. const numberOfChecks = flipVariations ? 3 : 1;
  123. for (let i = numberOfChecks; i > 0; i--) {
  124. const fittingPlacement = placements.find((placement) => {
  125. const checks = checksMap.get(placement);
  126. if (checks) {
  127. return checks.slice(0, i).every((check) => check);
  128. }
  129. });
  130. if (fittingPlacement) {
  131. firstFittingPlacement = fittingPlacement;
  132. break;
  133. }
  134. }
  135. }
  136. if (state.placement !== firstFittingPlacement) {
  137. state.modifiersData[name]._skip = true;
  138. state.placement = firstFittingPlacement;
  139. state.reset = true;
  140. }
  141. }
  142. // eslint-disable-next-line import/no-unused-modules
  143. export type FlipModifier = Modifier<'flip', Options>;
  144. export default ({
  145. name: 'flip',
  146. enabled: true,
  147. phase: 'main',
  148. fn: flip,
  149. requiresIfExists: ['offset'],
  150. data: { _skip: false },
  151. }: FlipModifier);