in packages/eui/src/services/popover/popover_positioning.ts [120:250]
export function findPopoverPosition({
anchor,
popover,
align,
position,
forcePosition,
buffer = 16,
offset = 0,
allowCrossAxis = true,
container,
arrowConfig,
returnBoundingBox,
}: FindPopoverPositionArgs): FindPopoverPositionResult {
// find the screen-relative bounding boxes of the anchor, popover, and container
const anchorBoundingBox = getElementBoundingBox(anchor);
const popoverBoundingBox = getElementBoundingBox(popover);
// calculate the window's bounds
// window.(innerWidth|innerHeight) do not account for scrollbars
// so prefer the clientWidth/clientHeight of the DOM if available
const documentWidth =
document.documentElement.clientWidth || window.innerWidth;
const documentHeight =
document.documentElement.clientHeight || window.innerHeight;
const windowBoundingBox: EuiClientRect = {
top: 0,
right: documentWidth,
bottom: documentHeight,
left: 0,
height: documentHeight,
width: documentWidth,
};
// if no container element is given fall back to using the window viewport
const containerBoundingBox = container
? getElementBoundingBox(container)
: windowBoundingBox;
/**
* `position` was specified by the function caller and is a strong hint
* as to the preferred location of the popover relative to the anchor.
* However, we strongly prefer showing all of the popover content within
* the window+container boundary and will iterate over the four
* possible sides until a perfect fit is located. If none of the locations
* fully contain popover, the location with the best fit is selected.
*
* This approach first checks the preferred `position`, then its opposite
* along the same axis, next a location on the cross-axis, and finally it
* tests the remaining position.
*
* e.g.
* if position = "top" the order is top, bottom, left right
* if position = "right" the order is right, left, top, bottom
*/
// Try the user-desired position first.
const iterationPositions = [position];
// keep user-defined alignment in the original positions.
const iterationAlignments: Array<undefined | EuiPopoverPosition> = [align];
if (forcePosition !== true) {
iterationPositions.push(positionComplements[position]); // Try the complementary position.
iterationAlignments.push(align); // keep user-defined alignment in the complementary position.
if (allowCrossAxis) {
iterationPositions.push(
positionSubstitutes[position], // Switch to the cross axis.
positionComplements[positionSubstitutes[position]] // Try the complementary position on the cross axis.
);
iterationAlignments.push(undefined, undefined); // discard desired alignment on cross-axis
}
} else {
// position is forced, if it conflicts with the alignment then reset align to `null`
// e.g. original placement request for `downLeft` is moved to the `left` side, future calls
// will position and align `left`, and `leftLeft` is not a valid placement
if (
position === align ||
(align !== undefined && position === positionComplements[align])
) {
iterationAlignments[0] = undefined;
}
}
let bestFit: number | undefined = undefined;
let bestPosition: FindPopoverPositionResult | null = null;
for (let idx = 0; idx < iterationPositions.length; idx++) {
const iterationPosition = iterationPositions[idx];
// See if we can find a position with a better fit than we've found so far.
const screenCoordinates = getPopoverScreenCoordinates({
position: iterationPosition,
align: iterationAlignments[idx],
anchorBoundingBox,
popoverBoundingBox,
windowBoundingBox,
containerBoundingBox,
offset,
buffer,
arrowConfig,
});
if (bestFit === undefined || screenCoordinates.fit > bestFit) {
bestFit = screenCoordinates.fit;
bestPosition = {
fit: screenCoordinates.fit,
position: iterationPosition,
top: screenCoordinates.top + window.scrollY,
left: screenCoordinates.left + window.scrollX,
arrow: screenCoordinates.arrow,
};
// If we've already found the ideal fit, use that position.
if (bestFit === 1) {
break;
}
}
// If we haven't improved the fit, then continue on and try a new position.
}
if (bestPosition == null) {
throw new Error('Failed to calculate bestPosition');
}
if (returnBoundingBox) {
bestPosition.anchorBoundingBox = anchorBoundingBox;
}
return bestPosition;
}