@@ -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.
@@ -894,27 +911,32 @@ export const calculateWindowAdjustment = (
894911
895912 /**
896913 * If not enough room for popover to appear
897- * above trigger, then cut it off.
914+ * above trigger, constrain to full viewport.
915+ * Pin both top and bottom to maximize visible area
916+ * and let the content scroll within those bounds.
898917 */
899918 } else {
919+ top = bodyPadding ;
900920 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- */
921+ checkSafeAreaTop = true ;
906922 checkSafeAreaBottom = true ;
923+ isFullyConstrained = true ;
907924 }
908925 }
909926
910927 /**
911928 * Final check: If the popover extends into any safe-area region,
912- * ensure the corresponding flag is set regardless of side .
929+ * constrain it to avoid overlapping system UI .
913930 * This handles cases where a side-positioned popover (left/right)
914- * still needs bottom safe-area padding because it extends into that region .
931+ * or a bottom-positioned popover extends into the safe area .
915932 */
916933 const popoverBottom = bottom !== undefined ? bodyHeight - bottom : top + contentHeight ;
917- if ( popoverBottom + safeAreaMargin > bodyHeight ) {
934+ if ( popoverBottom + safeAreaMargin > bodyHeight && bottom === undefined ) {
935+ /**
936+ * Popover extends into bottom safe area but isn't already constrained.
937+ * Set bottom to constrain the popover and apply safe-area adjustment.
938+ */
939+ bottom = bodyPadding ;
918940 checkSafeAreaBottom = true ;
919941 }
920942 if ( top < safeAreaMargin ) {
@@ -934,6 +956,7 @@ export const calculateWindowAdjustment = (
934956 arrowTop,
935957 arrowLeft,
936958 addPopoverBottomClass,
959+ isFullyConstrained,
937960 } ;
938961} ;
939962
0 commit comments