({
maxHeight: preferredMaxHeight,
menuEl,
minHeight,
placement: preferredPlacement,
shouldScroll,
isFixedPosition,
controlHeight,
}: PlacementArgs)
| 57 | } |
| 58 | |
| 59 | export function getMenuPlacement({ |
| 60 | maxHeight: preferredMaxHeight, |
| 61 | menuEl, |
| 62 | minHeight, |
| 63 | placement: preferredPlacement, |
| 64 | shouldScroll, |
| 65 | isFixedPosition, |
| 66 | controlHeight, |
| 67 | }: PlacementArgs): CalculatedMenuPlacementAndHeight { |
| 68 | const scrollParent = getScrollParent(menuEl!); |
| 69 | const defaultState: CalculatedMenuPlacementAndHeight = { |
| 70 | placement: 'bottom', |
| 71 | maxHeight: preferredMaxHeight, |
| 72 | }; |
| 73 | |
| 74 | // something went wrong, return default state |
| 75 | if (!menuEl || !menuEl.offsetParent) return defaultState; |
| 76 | |
| 77 | // we can't trust `scrollParent.scrollHeight` --> it may increase when |
| 78 | // the menu is rendered |
| 79 | const { height: scrollHeight } = scrollParent.getBoundingClientRect(); |
| 80 | const { |
| 81 | bottom: menuBottom, |
| 82 | height: menuHeight, |
| 83 | top: menuTop, |
| 84 | } = menuEl.getBoundingClientRect(); |
| 85 | |
| 86 | const { top: containerTop } = menuEl.offsetParent.getBoundingClientRect(); |
| 87 | const viewHeight = isFixedPosition |
| 88 | ? window.innerHeight |
| 89 | : normalizedHeight(scrollParent); |
| 90 | const scrollTop = getScrollTop(scrollParent); |
| 91 | |
| 92 | const marginBottom = parseInt(getComputedStyle(menuEl).marginBottom, 10); |
| 93 | const marginTop = parseInt(getComputedStyle(menuEl).marginTop, 10); |
| 94 | const viewSpaceAbove = containerTop - marginTop; |
| 95 | const viewSpaceBelow = viewHeight - menuTop; |
| 96 | const scrollSpaceAbove = viewSpaceAbove + scrollTop; |
| 97 | const scrollSpaceBelow = scrollHeight - scrollTop - menuTop; |
| 98 | |
| 99 | const scrollDown = menuBottom - viewHeight + scrollTop + marginBottom; |
| 100 | const scrollUp = scrollTop + menuTop - marginTop; |
| 101 | const scrollDuration = 160; |
| 102 | |
| 103 | switch (preferredPlacement) { |
| 104 | case 'auto': |
| 105 | case 'bottom': |
| 106 | // 1: the menu will fit, do nothing |
| 107 | if (viewSpaceBelow >= menuHeight) { |
| 108 | return { placement: 'bottom', maxHeight: preferredMaxHeight }; |
| 109 | } |
| 110 | |
| 111 | // 2: the menu will fit, if scrolled |
| 112 | if (scrollSpaceBelow >= menuHeight && !isFixedPosition) { |
| 113 | if (shouldScroll) { |
| 114 | animatedScrollTo(scrollParent, scrollDown, scrollDuration); |
| 115 | } |
| 116 |
no test coverage detected
searching dependent graphs…