@@ -37,6 +37,13 @@ export interface PopoverStyles {
3737 arrowTop : number ;
3838 arrowLeft : number ;
3939 addPopoverBottomClass : boolean ;
40+ /**
41+ * When true, the popover content was too tall to fit above or below
42+ * the trigger, so it was constrained to the full viewport height.
43+ * In this case, the arrow should be hidden as it cannot accurately
44+ * point to the trigger.
45+ */
46+ isFullyConstrained : boolean ;
4047}
4148
4249/**
@@ -835,6 +842,7 @@ export const calculateWindowAdjustment = (
835842 let checkSafeAreaBottom = false ;
836843 let checkSafeAreaLeft = false ;
837844 let checkSafeAreaRight = false ;
845+ let isFullyConstrained = false ;
838846 const triggerTop = triggerCoordinates
839847 ? triggerCoordinates . top + triggerCoordinates . height
840848 : bodyHeight / 2 - contentHeight / 2 ;
@@ -845,20 +853,29 @@ export const calculateWindowAdjustment = (
845853 * Adjust popover so it does not
846854 * go off the left of the screen.
847855 */
848- if ( left < bodyPadding + safeAreaMargin ) {
856+ if ( left < bodyPadding ) {
849857 left = bodyPadding ;
850- checkSafeAreaLeft = true ;
851858 originX = 'left' ;
852859 /**
853860 * Adjust popover so it does not
854861 * go off the right of the screen.
855862 */
856- } else if ( contentWidth + bodyPadding + left + safeAreaMargin > bodyWidth ) {
857- checkSafeAreaRight = true ;
863+ } else if ( contentWidth + bodyPadding + left > bodyWidth ) {
858864 left = bodyWidth - contentWidth - bodyPadding ;
859865 originX = 'right' ;
860866 }
861867
868+ /**
869+ * After position adjustment, check if popover is near edges
870+ * and needs safe-area CSS variable adjustments.
871+ */
872+ if ( left <= safeAreaMargin ) {
873+ checkSafeAreaLeft = true ;
874+ }
875+ if ( left + contentWidth >= bodyWidth - safeAreaMargin ) {
876+ checkSafeAreaRight = true ;
877+ }
878+
862879 /**
863880 * Adjust popover so it does not
864881 * go off the top of the screen.
@@ -892,29 +909,49 @@ export const calculateWindowAdjustment = (
892909 top = bodyPadding ;
893910 }
894911
912+ /**
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.
916+ *
917+ * We estimate the effective top by adding safeAreaMargin if checkSafeAreaTop
918+ * is true (since CSS will add the actual safe-area-top value).
919+ */
920+ const estimatedTop = checkSafeAreaTop ? top + safeAreaMargin : top ;
921+ if ( estimatedTop + contentHeight > bodyHeight - safeAreaMargin ) {
922+ bottom = bodyPadding ;
923+ checkSafeAreaBottom = true ;
924+ isFullyConstrained = true ;
925+ }
926+
895927 /**
896928 * If not enough room for popover to appear
897- * above trigger, then cut it off.
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.
898932 */
899933 } else {
934+ top = bodyPadding ;
900935 bottom = bodyPadding ;
901- /**
902- * When the popover is pinned to the bottom, account for safe area.
903- * This ensures the popover doesn't overlap with home indicators
904- * or navigation bars (e.g., Android API 36+ edge-to-edge).
905- */
936+ checkSafeAreaTop = true ;
906937 checkSafeAreaBottom = true ;
938+ isFullyConstrained = true ;
907939 }
908940 }
909941
910942 /**
911943 * Final check: If the popover extends into any safe-area region,
912- * ensure the corresponding flag is set regardless of side .
944+ * constrain it to avoid overlapping system UI .
913945 * This handles cases where a side-positioned popover (left/right)
914- * still needs bottom safe-area padding because it extends into that region .
946+ * or a bottom-positioned popover extends into the safe area .
915947 */
916948 const popoverBottom = bottom !== undefined ? bodyHeight - bottom : top + contentHeight ;
917- if ( popoverBottom + safeAreaMargin > bodyHeight ) {
949+ if ( popoverBottom + safeAreaMargin > bodyHeight && bottom === undefined ) {
950+ /**
951+ * Popover extends into bottom safe area but isn't already constrained.
952+ * Set bottom to constrain the popover and apply safe-area adjustment.
953+ */
954+ bottom = bodyPadding ;
918955 checkSafeAreaBottom = true ;
919956 }
920957 if ( top < safeAreaMargin ) {
@@ -934,6 +971,7 @@ export const calculateWindowAdjustment = (
934971 arrowTop,
935972 arrowLeft,
936973 addPopoverBottomClass,
974+ isFullyConstrained,
937975 } ;
938976} ;
939977
0 commit comments