in packages/eui/src/services/popover/popover_positioning.ts [437:554]
function getCrossAxisPosition({
crossAxisFirstSide,
crossAxisSecondSide,
crossAxisDimension,
position,
align,
buffer,
offset,
windowBoundingBox,
containerBoundingBox,
popoverBoundingBox,
anchorBoundingBox,
arrowConfig,
}: GetCrossAxisPositionArgs): CrossAxisPosition {
// how much of the popover overflows past either side of the anchor if its centered
const popoverSizeOnCrossAxis = popoverBoundingBox[crossAxisDimension];
const anchorSizeOnCrossAxis = anchorBoundingBox[crossAxisDimension];
const anchorHalfSize = anchorSizeOnCrossAxis / 2;
// the popover's original position on the cross-axis is determined by:
const crossAxisPositionOriginal =
anchorBoundingBox[crossAxisFirstSide] + // where the anchor is located
anchorHalfSize - // plus half anchor dimension
popoverSizeOnCrossAxis / 2; // less half the popover dimension
// To fit the content within both the window and container,
// compute the smaller of the two spaces along each edge
const combinedBoundingBox = intersectBoundingBoxes(
windowBoundingBox,
containerBoundingBox
);
const availableSpace = getAvailableSpace(
anchorBoundingBox,
combinedBoundingBox,
buffer,
offset,
position
);
const minimumSpace = arrowConfig ? arrowConfig.arrowBuffer : 0;
const contentOverflowSize =
(popoverSizeOnCrossAxis - anchorSizeOnCrossAxis) / 2;
let alignAmount = 0;
let alignDirection = 1;
let amountOfShiftNeeded = 0;
let shiftDirection = 1;
if (align != null) {
// no alignment, find how much the container boundary requires the content to shift
alignDirection = align === 'top' || align === 'left' ? 1 : -1;
alignAmount = contentOverflowSize;
const alignedOverflowAmount = contentOverflowSize + alignAmount;
const needsShift =
alignedOverflowAmount > availableSpace[positionComplements[align]];
amountOfShiftNeeded = needsShift
? alignedOverflowAmount - availableSpace[positionComplements[align]]
: 0;
shiftDirection = -1 * alignDirection;
} else {
// shifting the popover to one side may yield a better fit
const spaceAvailableOnFirstSide = availableSpace[crossAxisFirstSide];
const spaceAvailableOnSecondSide = availableSpace[crossAxisSecondSide];
const isShiftTowardFirstSide =
spaceAvailableOnFirstSide > spaceAvailableOnSecondSide;
shiftDirection = isShiftTowardFirstSide ? -1 : 1;
// determine which direction has more room and the popover should shift to
const leastAvailableSpace = Math.min(
spaceAvailableOnFirstSide,
spaceAvailableOnSecondSide
);
const needsShift = contentOverflowSize > leastAvailableSpace;
amountOfShiftNeeded = needsShift
? contentOverflowSize - leastAvailableSpace
: 0;
}
// shift over the popover if necessary
const shiftAmount = amountOfShiftNeeded * shiftDirection;
let crossAxisPosition =
crossAxisPositionOriginal + shiftAmount + alignAmount * alignDirection;
// if an `arrowConfig` is specified, find where to position the arrow
let crossAxisArrowPosition;
if (arrowConfig) {
const { arrowWidth } = arrowConfig;
crossAxisArrowPosition =
anchorBoundingBox[crossAxisFirstSide] + anchorHalfSize - arrowWidth / 2;
// make sure there's enough buffer around the arrow
// by calculating how how much the arrow would need to move
// but instead of moving the arrow, shift the popover content
if (crossAxisArrowPosition < crossAxisPosition + minimumSpace) {
// arrow is too close to the minimum side
const difference =
crossAxisPosition + minimumSpace - crossAxisArrowPosition;
crossAxisPosition -= difference;
} else if (
crossAxisArrowPosition + minimumSpace + arrowWidth >
crossAxisPosition + popoverSizeOnCrossAxis
) {
// arrow is too close to the maximum side
const edge = crossAxisPosition + popoverSizeOnCrossAxis;
const difference =
crossAxisArrowPosition - (edge - minimumSpace - arrowWidth);
crossAxisPosition += difference;
}
}
return {
crossAxisPosition,
crossAxisArrowPosition,
};
}