export function findPopoverPosition()

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;
}