@@ -884,7 +884,19 @@ export const calculateWindowAdjustment = (
884884 * margins.
885885 */
886886 if ( triggerTop + triggerHeight + contentHeight > bodyHeight && ( side === 'top' || side === 'bottom' ) ) {
887- if ( triggerTop - contentHeight > 0 ) {
887+ /**
888+ * Calculate available space above and below, accounting for safe areas.
889+ * This ensures we flip to whichever side has more usable space.
890+ */
891+ const spaceAbove = ( triggerCoordinates ?. top ?? triggerTop ) - bodyPadding - safeAreaMargin ;
892+ const spaceBelow = bodyHeight - triggerTop - triggerHeight - bodyPadding - safeAreaMargin ;
893+
894+ /**
895+ * Flip above if:
896+ * 1. Content fits entirely above the trigger, OR
897+ * 2. There's more usable space above than below (accounting for safe areas)
898+ */
899+ if ( triggerTop - contentHeight > 0 || spaceAbove > spaceBelow ) {
888900 /**
889901 * While we strive to align the popover with the trigger
890902 * on smaller screens this is not always possible. As a result,
@@ -910,25 +922,27 @@ export const calculateWindowAdjustment = (
910922 }
911923
912924 /**
913- * After flipping above, check if popover still extends into bottom safe area.
914- * This can happen when the popover is taller than the available space between
915- * the top safe area and the trigger. In this case, constrain with bottom too.
925+ * After flipping above, check if popover will likely overflow the viewport.
926+ * This can happen when the popover is taller than the available space.
916927 *
917- * We estimate the effective top by adding safeAreaMargin if checkSafeAreaTop
918- * is true (since CSS will add the actual safe-area-top value).
928+ * When checkSafeAreaTop is true, the CSS will add safe-area-top to the
929+ * top position, pushing the popover down. Since we don't know the exact
930+ * CSS safe-area value, we use a threshold that accounts for likely
931+ * safe-area sizes. This only triggers when:
932+ * 1. We're already applying safe-area-top (checkSafeAreaTop), and
933+ * 2. The popover is close enough to overflowing that any safe-area
934+ * would push it past the viewport
919935 */
920- const estimatedTop = checkSafeAreaTop ? top + safeAreaMargin : top ;
921- if ( estimatedTop + contentHeight > bodyHeight - safeAreaMargin ) {
936+ if ( checkSafeAreaTop && top + contentHeight > bodyHeight - safeAreaMargin - bodyPadding ) {
922937 bottom = bodyPadding ;
923938 checkSafeAreaBottom = true ;
924939 isFullyConstrained = true ;
925940 }
926941
927942 /**
928- * If not enough room for popover to appear
929- * above trigger, constrain to full viewport.
930- * Pin both top and bottom to maximize visible area
931- * and let the content scroll within those bounds.
943+ * If not enough room for popover to appear above trigger
944+ * (i.e., content is taller than space above), then constrain
945+ * the popover to fill the entire viewport from top to bottom.
932946 */
933947 } else {
934948 top = bodyPadding ;
@@ -940,19 +954,19 @@ export const calculateWindowAdjustment = (
940954 }
941955
942956 /**
943- * Final check: If the popover extends into any safe-area region,
944- * constrain it to avoid overlapping system UI.
945- * This handles cases where a side-positioned popover (left/right)
946- * or a bottom-positioned popover extends into the safe area .
957+ * Check if popover is near edges and needs safe-area adjustments.
958+ * When the popover extends into the safe-area zone, set a bottom constraint
959+ * to push it up and out of the unsafe area. This is essential for
960+ * edge-to-edge displays on Android API 36+ and iOS devices with home indicators .
947961 */
948962 const popoverBottom = bottom !== undefined ? bodyHeight - bottom : top + contentHeight ;
949- if ( popoverBottom + safeAreaMargin > bodyHeight && bottom === undefined ) {
963+ if ( popoverBottom > bodyHeight - safeAreaMargin && bottom === undefined ) {
964+ checkSafeAreaBottom = true ;
950965 /**
951- * Popover extends into bottom safe area but isn't already constrained .
952- * Set bottom to constrain the popover and apply safe-area adjustment .
966+ * Set a bottom constraint to push the popover up out of the safe-area zone .
967+ * The animation will add the safe-area CSS variable to this value .
953968 */
954969 bottom = bodyPadding ;
955- checkSafeAreaBottom = true ;
956970 }
957971 if ( top < safeAreaMargin ) {
958972 checkSafeAreaTop = true ;
0 commit comments