Skip to content

Commit

Permalink
debug nested popover height when positioned above
Browse files Browse the repository at this point in the history
  • Loading branch information
chloerice committed Apr 17, 2024
1 parent f55e5ad commit acad2d5
Showing 1 changed file with 257 additions and 109 deletions.
366 changes: 257 additions & 109 deletions polaris-react/src/components/PositionedOverlay/utilities/math.ts
@@ -1,6 +1,14 @@
import {Rect} from '../../../utilities/geometry';

export type PreferredPosition = 'above' | 'below' | 'mostSpace' | 'cover';
const MINIMUM_SURROUNDING_SPACE = 16;

export type PreferredPosition =
| 'above'
| 'below'
| 'mostSpace'
| 'right'
| 'left'
| 'cover';

export type PreferredAlignment = 'left' | 'center' | 'right';

Expand All @@ -20,133 +28,273 @@ export function calculateVerticalPosition(
fixed: boolean | undefined,
topBarOffset = 0,
) {
const positionedHorizontal =
preferredPosition === 'right' || preferredPosition === 'left';
const hasScrollBar = scrollableContainerRect.height > containerRect.height;
const activatorTop = activatorRect.top;
const activatorBottom = activatorTop + activatorRect.height;
const spaceAbove = activatorRect.top - topBarOffset;
const spaceBelow =
containerRect.height - activatorRect.top - activatorRect.height;

const desiredHeight = overlayRect.height;
const verticalMargins = overlayMargins.activator + overlayMargins.container;
const minimumSpaceToScroll = overlayMargins.container;
const distanceToTopScroll =
activatorRect.top - Math.max(scrollableContainerRect.top, 0);
const distanceToBottomScroll =
containerRect.top +
Math.min(
containerRect.height,
scrollableContainerRect.top + scrollableContainerRect.height,
) -
(activatorRect.top + activatorRect.height);
const enoughSpaceFromTopScroll = distanceToTopScroll >= minimumSpaceToScroll;
const enoughSpaceFromBottomScroll =
distanceToBottomScroll >= minimumSpaceToScroll;
const heightIfAbove = Math.min(spaceAbove, desiredHeight);
const heightIfBelow = Math.min(spaceBelow, desiredHeight);
const heightIfAboveCover = Math.min(
spaceAbove + activatorRect.height,
desiredHeight,
);
const heightIfBelowCover = Math.min(
spaceBelow + activatorRect.height,
desiredHeight,
);
const minimumSurroundingSpace = verticalMargins
? verticalMargins
: MINIMUM_SURROUNDING_SPACE;
const spaceAbove = positionedHorizontal
? activatorRect.top + activatorRect.height - topBarOffset
: activatorRect.top - topBarOffset;
const spaceBelow = positionedHorizontal
? containerRect.height - activatorRect.top
: containerRect.height - activatorRect.top - activatorRect.height;
const desiredHeight = overlayRect.height;
const enoughSpaceFromTopEdge =
spaceAbove >= desiredHeight + minimumSurroundingSpace;
const enoughSpaceFromBottomEdge =
spaceBelow >= desiredHeight + minimumSurroundingSpace;
const containerRectTop = fixed ? 0 : containerRect.top;
const mostSpaceOnTop =
(enoughSpaceFromTopEdge ||
(spaceAbove >= spaceBelow && !enoughSpaceFromBottomEdge)) &&
(spaceAbove >= desiredHeight || spaceAbove >= spaceBelow);

const positionIfAbove = {
height: heightIfAbove - verticalMargins,
top: activatorTop + containerRectTop - heightIfAbove,
positioning: 'above',
};
const mostSpaceOnBottom =
(enoughSpaceFromBottomEdge ||
(spaceBelow >= spaceAbove && !enoughSpaceFromTopEdge)) &&
(spaceBelow >= desiredHeight || spaceBelow >= spaceAbove);

const positionIfBelow = {
height: heightIfBelow - verticalMargins,
top: activatorBottom + containerRectTop,
positioning: 'below',
};
const heightIfAbove = enoughSpaceFromTopEdge
? desiredHeight - verticalMargins
: spaceAbove - minimumSurroundingSpace;
const heightIfBelow = enoughSpaceFromBottomEdge
? desiredHeight - verticalMargins
: spaceBelow - minimumSurroundingSpace;

const positionIfCoverBelow = {
height: heightIfBelowCover - verticalMargins,
top: activatorTop + containerRectTop,
positioning: 'cover',
};
const positionIfAbove =
activatorTop + containerRectTop - heightIfAbove - verticalMargins;
const positionIfBelow = activatorBottom + containerRectTop;

const positionIfCoverAbove = {
height: heightIfAboveCover - verticalMargins,
top:
activatorTop +
containerRectTop -
heightIfAbove +
activatorRect.height +
verticalMargins,
positioning: 'cover',
};
console.table([
{variable: 'activatorRect.top', value: activatorRect.top},
{variable: 'containerRect.top', value: containerRect.top},
{
variable: 'scrollableContainerRect.top',
value: scrollableContainerRect.top,
},

if (preferredPosition === 'above') {
return (enoughSpaceFromTopScroll ||
(distanceToTopScroll >= distanceToBottomScroll &&
!enoughSpaceFromBottomScroll)) &&
(spaceAbove > desiredHeight || spaceAbove > spaceBelow)
? positionIfAbove
: positionIfBelow;
}
{variable: 'overlayRect.height', value: overlayRect.height},
{variable: 'activatorRect.height', value: activatorRect.height},
{variable: 'containerRect.height', value: containerRect.height},
{
variable: 'scrollableContainerRect.height',
value: scrollableContainerRect.height,
},
{variable: 'verticalMargins', value: verticalMargins},
{
variable: 'spaceAbove',
value: spaceAbove,
},
{
variable: 'spaceBelow',
value: spaceBelow,
},
{
variable: 'enoughSpaceFromTopEdge',
value: enoughSpaceFromTopEdge,
},
{
variable: 'enoughSpaceFromBottomEdge',
value: enoughSpaceFromBottomEdge,
},
{
variable: 'mostSpaceOnTop',
value: mostSpaceOnTop,
},
{
variable: 'mostSpaceOnBottom',
value: mostSpaceOnBottom,
},
{
variable: 'positionIfAbove',
value: positionIfAbove,
},
{
variable: 'positionIfBelow',
value: positionIfBelow,
},
{variable: 'desiredHeight', value: desiredHeight},
{
variable: 'heightIfAbove',
value: heightIfAbove,
},
{
variable: 'heightIfBelow',
value: heightIfBelow,
},
{
variable: 'hasScrollBar',
value: scrollableContainerRect.height > containerRect.height,
},
]);

if (preferredPosition === 'below') {
return (enoughSpaceFromBottomScroll ||
(distanceToBottomScroll >= distanceToTopScroll &&
!enoughSpaceFromTopScroll)) &&
(spaceBelow > desiredHeight || spaceBelow > spaceAbove)
? positionIfBelow
: positionIfAbove;
}
if (!positionedHorizontal) {
if (preferredPosition === 'above') {
return mostSpaceOnTop
? {
height: heightIfAbove,
top: positionIfAbove,
positioning: 'above',
}
: {
height: heightIfBelow,
top: positionIfBelow,
positioning: 'below',
};
}

if (preferredPosition === 'cover') {
return (enoughSpaceFromBottomScroll ||
(distanceToBottomScroll >= distanceToTopScroll &&
!enoughSpaceFromTopScroll)) &&
(spaceBelow + activatorRect.height > desiredHeight ||
spaceBelow > spaceAbove)
? positionIfCoverBelow
: positionIfCoverAbove;
if (preferredPosition === 'below') {
return mostSpaceOnBottom
? {
height: heightIfBelow,
top: positionIfBelow,
positioning: 'below',
}
: {
height: heightIfAbove,
top: positionIfAbove,
positioning: 'above',
};
}
}

if (enoughSpaceFromTopScroll && enoughSpaceFromBottomScroll) {
return spaceAbove > spaceBelow ? positionIfAbove : positionIfBelow;
// if (positionedHorizontal) {
// positionIfAbove = !hasScrollBar
// ? containerRect.height - activatorBottom
// : scrollableContainerRect.bottom - activatorBottom;
// positionIfBelow = activatorTop + containerRect.top;

// return mostSpaceOnBottom
// ? {
// height: heightIfBelow,
// top: positionIfBelow,
// positioning: 'below',
// }
// : {
// height: heightIfAbove,
// bottom: positionIfAbove,
// positioning: 'above',
// };
// }

if (enoughSpaceFromTopEdge && enoughSpaceFromBottomEdge) {
return {height: heightIfBelow, top: positionIfBelow, positioning: 'below'};
}

return distanceToTopScroll > minimumSpaceToScroll
? positionIfAbove
: positionIfBelow;
return mostSpaceOnBottom
? {height: heightIfBelow, top: positionIfBelow, positioning: 'below'}
: {height: heightIfAbove, top: positionIfAbove, positioning: 'above'};
}

export function calculateHorizontalPosition(
activatorRect: Rect,
overlayRect: Rect,
containerRect: Rect,
overlayMargins: Margins,
preferredAlignment: PreferredAlignment,
) {
const maximum = containerRect.width - overlayRect.width;

if (preferredAlignment === 'left') {
return Math.min(
maximum,
Math.max(0, activatorRect.left - overlayMargins.horizontal),
);
} else if (preferredAlignment === 'right') {
const activatorRight =
containerRect.width - (activatorRect.left + activatorRect.width);

return Math.min(
maximum,
Math.max(0, activatorRight - overlayMargins.horizontal),
);
interface HorizontalPosition {
activatorRect: Rect;
overlayRect: Rect;
containerRect: Rect;
overlayMargins: Margins;
preferredAlignment: PreferredAlignment;
preferredHorizontalPosition?: 'left' | 'right';
overlayMinWidth: number;
}

export function calculateHorizontalPosition({
activatorRect,
overlayRect,
containerRect,
overlayMargins,
preferredAlignment,
preferredHorizontalPosition,
overlayMinWidth = 0,
}: HorizontalPosition) {
const maximumWidth = containerRect.width - overlayRect.width;
const activatorRight =
containerRect.width - (activatorRect.left + activatorRect.width);

const minimumSurroundingSpace = overlayMargins.horizontal
? overlayMargins.horizontal
: MINIMUM_SURROUNDING_SPACE;
const desiredWidth = overlayRect.width;
const distanceToLeftEdge = activatorRect.left;
const distanceToRightEdge = containerRect.width - activatorRight;
const enoughSpaceFromLeftEdge =
distanceToLeftEdge >=
(overlayMinWidth || desiredWidth) + minimumSurroundingSpace;
const enoughSpaceFromRightEdge =
distanceToRightEdge >=
(overlayMinWidth || desiredWidth) + minimumSurroundingSpace;

if (!preferredHorizontalPosition) {
if (preferredAlignment === 'left') {
return {
left: Math.min(
maximumWidth,
Math.max(0, activatorRect.left - minimumSurroundingSpace),
),
width: null,
};
} else if (preferredAlignment === 'right') {
return {
left: Math.min(
maximumWidth,
Math.max(0, activatorRight - minimumSurroundingSpace),
),
width: null,
};
}
}

return Math.min(
maximum,
Math.max(0, activatorRect.center.x - overlayRect.width / 2),
);
if (preferredHorizontalPosition) {
const positionIfRight = activatorRect.left + activatorRect.width;
const positionIfLeft = containerRect.width - activatorRect.left;

const widthIfLeft = enoughSpaceFromLeftEdge
? desiredWidth
: Math.min(distanceToLeftEdge - minimumSurroundingSpace, desiredWidth);

const widthIfRight = enoughSpaceFromRightEdge
? desiredWidth
: Math.min(distanceToRightEdge - minimumSurroundingSpace, desiredWidth);

if (preferredHorizontalPosition === 'right') {
return enoughSpaceFromRightEdge
? {
left: positionIfRight,
width:
overlayMinWidth && widthIfRight < overlayMinWidth
? overlayMinWidth
: null,
}
: {right: positionIfLeft, width: null};
} else {
return enoughSpaceFromLeftEdge
? {
right: positionIfLeft,
width:
overlayMinWidth && widthIfLeft < overlayMinWidth
? overlayMinWidth
: null,
}
: {
left: positionIfRight,
width: null,
};
}
}

return {
width: null,
left: Math.min(
maximumWidth,
Math.max(
0,
activatorRect.center.x - overlayRect.width / 2 + containerRect.left,
),
),
};
}

export function rectIsOutsideOfRect(inner: Rect, outer: Rect) {
Expand Down

0 comments on commit acad2d5

Please sign in to comment.