|
function pureCalculateAnchoredPosition( |
|
viewportRect: BoxPosition, |
|
relativePosition: Position, |
|
floatingRect: Size, |
|
anchorRect: BoxPosition, |
|
{side, align, allowOutOfBounds, anchorOffset, alignmentOffset}: PositionSettings, |
|
): AnchorPosition { |
|
// Compute the relative viewport rect, to bring it into the same coordinate space as `pos` |
|
const relativeViewportRect: BoxPosition = { |
|
top: viewportRect.top - relativePosition.top, |
|
left: viewportRect.left - relativePosition.left, |
|
width: viewportRect.width, |
|
height: viewportRect.height, |
|
} |
|
|
|
let pos = calculatePosition(floatingRect, anchorRect, side, align, anchorOffset, alignmentOffset) |
|
let anchorSide = side |
|
let anchorAlign = align |
|
pos.top -= relativePosition.top |
|
pos.left -= relativePosition.left |
|
|
|
// Handle screen overflow |
|
if (!allowOutOfBounds) { |
|
const alternateOrder = alternateOrders[side] |
|
|
|
let positionAttempt = 0 |
|
if (alternateOrder) { |
|
let prevSide = side |
|
|
|
// Try all the alternate sides until one does not overflow |
|
while ( |
|
positionAttempt < alternateOrder.length && |
|
shouldRecalculatePosition(prevSide, pos, relativeViewportRect, floatingRect) |
|
) { |
|
const nextSide = alternateOrder[positionAttempt++] |
|
prevSide = nextSide |
|
|
|
// If we have cut off in the same dimension as the "side" option, try flipping to the opposite side. |
|
pos = calculatePosition(floatingRect, anchorRect, nextSide, align, anchorOffset, alignmentOffset) |
|
pos.top -= relativePosition.top |
|
pos.left -= relativePosition.left |
|
anchorSide = nextSide |
|
} |
|
} |
|
|
|
// If using alternate anchor side does not stop overflow, |
|
// try using an alternate alignment |
|
const alternateAlignment = alternateAlignments[align] |
|
|
|
let alignmentAttempt = 0 |
|
if (alternateAlignment) { |
|
let prevAlign = align |
|
|
|
// Try all the alternate alignments until one does not overflow |
|
while ( |
|
alignmentAttempt < alternateAlignment.length && |
|
shouldRecalculateAlignment(prevAlign, pos, relativeViewportRect, floatingRect) |
|
) { |
|
const nextAlign = alternateAlignment[alignmentAttempt++] |
|
prevAlign = nextAlign |
|
|
|
pos = calculatePosition(floatingRect, anchorRect, anchorSide, nextAlign, anchorOffset, alignmentOffset) |
|
pos.top -= relativePosition.top |
|
pos.left -= relativePosition.left |
|
anchorAlign = nextAlign |
|
} |
|
} |
|
|
|
// At this point we've flipped the position if applicable. Now just nudge until it's on-screen. |
|
if (pos.top < relativeViewportRect.top) { |
|
pos.top = relativeViewportRect.top |
|
} |
|
if (pos.left < relativeViewportRect.left) { |
|
pos.left = relativeViewportRect.left |
|
} |
|
if (pos.left + floatingRect.width > viewportRect.width + relativeViewportRect.left) { |
|
pos.left = viewportRect.width + relativeViewportRect.left - floatingRect.width |
|
} |
|
// If we have exhausted all possible positions and none of them worked, we |
|
// say that overflowing the bottom of the screen is acceptable since it is |
|
// likely to be able to scroll. |
|
if (alternateOrder && positionAttempt < alternateOrder.length) { |
|
if (pos.top + floatingRect.height > viewportRect.height + relativeViewportRect.top) { |
|
pos.top = viewportRect.height + relativeViewportRect.top - floatingRect.height |
|
} |
|
} |
|
} |
|
|
|
return {...pos, anchorSide, anchorAlign} |
|
} |