in src/common/PopupContainerViewBase.tsx [56:235]
export function recalcPositionFromLayoutData(windowDims: Dimensions, anchorRect: ClientRect, popupRect: Dimensions,
positionPriorities?: PopupPosition[], useInnerPositioning?: boolean): RecalcResult | undefined {
// If the anchor has disappeared, dismiss the popup.
if (!(anchorRect.width > 0 && anchorRect.height > 0)) {
return undefined;
}
// If the anchor is no longer in the window's bounds, cancel the popup.
if (anchorRect.left >= windowDims.width || anchorRect.right <= 0 ||
anchorRect.bottom <= 0 || anchorRect.top >= windowDims.height) {
return undefined;
}
let positionsToTry = positionPriorities;
if (!positionsToTry || positionsToTry.length === 0) {
positionsToTry = ['bottom', 'right', 'top', 'left'];
}
if (positionsToTry.length === 1 && positionsToTry[0] === 'context') {
// Context only works with exact matches, so fall back to walking around the compass if it doesn't fit exactly.
positionsToTry.push('bottom', 'right', 'top', 'left');
}
if (useInnerPositioning) {
// If the popup is meant to be shown inside the anchor we need to recalculate
// the position differently.
return recalcInnerPosition(anchorRect, positionsToTry[0], popupRect.width, popupRect.height);
}
// Start by assuming that we'll be unconstrained.
const result: RecalcResult = {
popupX: 0,
popupY: 0,
anchorOffset: 0,
anchorPosition: 'top',
constrainedPopupWidth: popupRect.width,
constrainedPopupHeight: popupRect.height,
};
let foundPerfectFit = false;
let foundPartialFit = false;
positionsToTry.forEach(pos => {
if (!foundPerfectFit) {
let absX = 0;
let absY = 0;
let anchorOffset = 0;
let constrainedWidth = 0;
let constrainedHeight = 0;
switch (pos) {
case 'bottom':
absY = anchorRect.bottom;
absX = anchorRect.left + (anchorRect.width - popupRect.width) / 2;
anchorOffset = popupRect.width / 2;
if (popupRect.height <= windowDims.height - ALLEY_WIDTH - anchorRect.bottom) {
foundPerfectFit = true;
} else if (!foundPartialFit) {
constrainedHeight = windowDims.height - ALLEY_WIDTH - anchorRect.bottom;
}
break;
case 'top':
absY = anchorRect.top - popupRect.height;
absX = anchorRect.left + (anchorRect.width - popupRect.width) / 2;
anchorOffset = popupRect.width / 2;
if (popupRect.height <= anchorRect.top - ALLEY_WIDTH) {
foundPerfectFit = true;
} else if (!foundPartialFit) {
constrainedHeight = anchorRect.top - ALLEY_WIDTH;
}
break;
case 'right':
absX = anchorRect.right;
absY = anchorRect.top + (anchorRect.height - popupRect.height) / 2;
anchorOffset = popupRect.height / 2;
if (popupRect.width <= windowDims.width - ALLEY_WIDTH - anchorRect.right) {
foundPerfectFit = true;
} else if (!foundPartialFit) {
constrainedWidth = windowDims.width - ALLEY_WIDTH - anchorRect.right;
}
break;
case 'left':
absX = anchorRect.left - popupRect.width;
absY = anchorRect.top + (anchorRect.height - popupRect.height) / 2;
anchorOffset = popupRect.height / 2;
if (popupRect.width <= anchorRect.left - ALLEY_WIDTH) {
foundPerfectFit = true;
} else if (!foundPartialFit) {
constrainedWidth = anchorRect.left - ALLEY_WIDTH;
}
break;
case 'context': {
// Search for perfect fits on the LR, LL, TR, and TL corners.
const fitsAbove = anchorRect.top - popupRect.height >= ALLEY_WIDTH;
const fitsBelow = anchorRect.top + anchorRect.height + popupRect.height <= windowDims.height - ALLEY_WIDTH;
const fitsLeft = anchorRect.left - popupRect.width >= ALLEY_WIDTH;
const fitsRight = anchorRect.left + anchorRect.width + popupRect.width <= windowDims.width - ALLEY_WIDTH;
if (fitsBelow && fitsRight) {
foundPerfectFit = true;
absX = anchorRect.left + anchorRect.width;
absY = anchorRect.top + anchorRect.height;
} else if (fitsBelow && fitsLeft) {
foundPerfectFit = true;
absX = anchorRect.left - popupRect.width;
absY = anchorRect.top + anchorRect.height;
} else if (fitsAbove && fitsRight) {
foundPerfectFit = true;
absX = anchorRect.left + anchorRect.width;
absY = anchorRect.top - popupRect.height;
} else if (fitsAbove && fitsLeft) {
foundPerfectFit = true;
absX = anchorRect.left - popupRect.width;
absY = anchorRect.top - popupRect.height;
}
break;
}
}
const effectiveWidth = constrainedWidth || popupRect.width;
const effectiveHeight = constrainedHeight || popupRect.height;
// Make sure we're not hanging off the bounds of the window.
if (absX < ALLEY_WIDTH) {
if (pos === 'top' || pos === 'bottom') {
anchorOffset -= ALLEY_WIDTH - absX;
if (anchorOffset < MIN_ANCHOR_OFFSET || anchorOffset > effectiveWidth - MIN_ANCHOR_OFFSET) {
foundPerfectFit = false;
}
}
absX = ALLEY_WIDTH;
} else if (absX > windowDims.width - ALLEY_WIDTH - effectiveWidth) {
if (pos === 'top' || pos === 'bottom') {
anchorOffset -= (windowDims.width - ALLEY_WIDTH - effectiveWidth - absX);
if (anchorOffset < MIN_ANCHOR_OFFSET || anchorOffset > effectiveWidth - MIN_ANCHOR_OFFSET) {
foundPerfectFit = false;
}
}
absX = windowDims.width - ALLEY_WIDTH - effectiveWidth;
}
if (absY < ALLEY_WIDTH) {
if (pos === 'right' || pos === 'left') {
anchorOffset += absY - ALLEY_WIDTH;
if (anchorOffset < MIN_ANCHOR_OFFSET || anchorOffset > effectiveHeight - MIN_ANCHOR_OFFSET) {
foundPerfectFit = false;
}
}
absY = ALLEY_WIDTH;
} else if (absY > windowDims.height - ALLEY_WIDTH - effectiveHeight) {
if (pos === 'right' || pos === 'left') {
anchorOffset -= (windowDims.height - ALLEY_WIDTH - effectiveHeight - absY);
if (anchorOffset < MIN_ANCHOR_OFFSET || anchorOffset > effectiveHeight - MIN_ANCHOR_OFFSET) {
foundPerfectFit = false;
}
}
absY = windowDims.height - ALLEY_WIDTH - effectiveHeight;
}
if (foundPerfectFit || effectiveHeight > 0 || effectiveWidth > 0) {
result.popupY = absY;
result.popupX = absX;
result.anchorOffset = anchorOffset;
result.anchorPosition = pos;
result.constrainedPopupWidth = effectiveWidth;
result.constrainedPopupHeight = effectiveHeight;
foundPartialFit = true;
}
}
});
return result;
}