@@ -1456,21 +1456,64 @@ function RemoteFunctions(config = {}) {
14561456 _getBoxPosition : function ( boxWidth , boxHeight ) {
14571457 const elemBounds = this . element . getBoundingClientRect ( ) ;
14581458 const offset = _screenOffset ( this . element ) ;
1459+ const MARGIN = 6 ;
14591460
1460- let topPos = offset . top - boxHeight - 6 ; // 6 for just some little space to breathe
1461- let leftPos = offset . left + elemBounds . width - boxWidth ;
1461+ const viewportLeft = window . scrollX ;
1462+ const viewportRight = window . scrollX + window . innerWidth ;
1463+ const viewportTop = window . scrollY ;
1464+ const viewportBottom = window . scrollY + window . innerHeight ;
14621465
1463- // Check if the box would go off the top of the viewport
1464- if ( elemBounds . top - boxHeight < 6 ) {
1465- topPos = offset . top + elemBounds . height + 6 ;
1466+ // Default: Top of element, left-aligned
1467+ let topPos = offset . top - boxHeight - MARGIN ;
1468+ let leftPos = offset . left ;
1469+
1470+ // Check if element is very tall (covers full viewport height)
1471+ const isVeryTall = elemBounds . height >= window . innerHeight - 50 ;
1472+
1473+ if ( isVeryTall ) {
1474+ // Place inside element at top
1475+ topPos = offset . top + MARGIN ;
1476+ leftPos = offset . left + MARGIN ;
1477+ return { topPos, leftPos} ;
14661478 }
14671479
1468- // Check if the box would go off the left of the viewport
1469- if ( leftPos < 0 ) {
1470- leftPos = offset . left ;
1480+ // Edge Case: Goes off top
1481+ if ( topPos < viewportTop + MARGIN ) {
1482+ // Try right side
1483+ const rightSideTop = offset . top ;
1484+ const rightSideLeft = offset . left + elemBounds . width + MARGIN ;
1485+
1486+ if ( rightSideLeft + boxWidth <= viewportRight - MARGIN ) {
1487+ topPos = rightSideTop ;
1488+ leftPos = rightSideLeft ;
1489+ } else {
1490+ // Try left side
1491+ const leftSideLeft = offset . left - boxWidth - MARGIN ;
1492+
1493+ if ( leftSideLeft >= viewportLeft + MARGIN ) {
1494+ topPos = offset . top ;
1495+ leftPos = leftSideLeft ;
1496+ } else {
1497+ // Last resort: Below element (below info box)
1498+ // Will be positioned below info box by checking overlap later
1499+ topPos = offset . top + elemBounds . height + MARGIN ;
1500+ leftPos = offset . left ;
1501+ }
1502+ }
14711503 }
14721504
1473- return { topPos : topPos , leftPos : leftPos } ;
1505+ // Handle horizontal viewport boundaries
1506+ if ( leftPos + boxWidth > viewportRight - MARGIN ) {
1507+ // Calculate exact overflow and shift left
1508+ const overflow = ( leftPos + boxWidth ) - ( viewportRight - MARGIN ) ;
1509+ leftPos -= overflow ;
1510+ }
1511+
1512+ if ( leftPos < viewportLeft + MARGIN ) {
1513+ leftPos = viewportLeft + MARGIN ;
1514+ }
1515+
1516+ return { topPos, leftPos} ;
14741517 } ,
14751518
14761519 _style : function ( ) {
@@ -1976,73 +2019,103 @@ function RemoteFunctions(config = {}) {
19762019 _getBoxPosition : function ( boxDimensions , overlap = false ) {
19772020 const elemBounds = this . element . getBoundingClientRect ( ) ;
19782021 const offset = _screenOffset ( this . element ) ;
1979- let topPos = 0 ;
1980- let leftPos = 0 ;
2022+ const MARGIN = 6 ;
2023+
2024+ const viewportLeft = window . scrollX ;
2025+ const viewportRight = window . scrollX + window . innerWidth ;
2026+ const viewportTop = window . scrollY ;
2027+ const viewportBottom = window . scrollY + window . innerHeight ;
2028+
2029+ // Default: Bottom of element, left-aligned
2030+ let topPos = offset . top + elemBounds . height + MARGIN ;
2031+ let leftPos = offset . left ;
2032+
2033+ // Check if element is very tall (covers full viewport height)
2034+ const isVeryTall = elemBounds . height >= window . innerHeight - 50 ;
19812035
1982- if ( overlap ) {
1983- topPos = offset . top + 2 ;
1984- leftPos = offset . left + elemBounds . width + 6 ; // positioning at the right side
2036+ if ( isVeryTall ) {
2037+ // Place inside element at bottom
2038+ topPos = offset . top + elemBounds . height - boxDimensions . height - MARGIN ;
2039+ leftPos = offset . left + MARGIN ;
2040+ return { topPos, leftPos} ;
2041+ }
2042+
2043+ // Edge Case: Goes off bottom
2044+ if ( topPos + boxDimensions . height > viewportBottom - MARGIN ) {
2045+ // Try right side
2046+ const rightSideTop = offset . top ;
2047+ const rightSideLeft = offset . left + elemBounds . width + MARGIN ;
19852048
1986- // Check if overlap position would go off the right of the viewport
1987- if ( leftPos + boxDimensions . width > window . innerWidth ) {
1988- leftPos = offset . left - boxDimensions . width - 6 ; // positioning at the left side
2049+ if ( rightSideLeft + boxDimensions . width <= viewportRight - MARGIN ) {
2050+ topPos = rightSideTop ;
2051+ leftPos = rightSideLeft ;
2052+ } else {
2053+ // Try left side
2054+ const leftSideLeft = offset . left - boxDimensions . width - MARGIN ;
19892055
1990- if ( leftPos < 0 ) { // if left positioning not perfect, position at bottom
1991- topPos = offset . top + elemBounds . height + 6 ;
2056+ if ( leftSideLeft >= viewportLeft + MARGIN ) {
2057+ topPos = offset . top ;
2058+ leftPos = leftSideLeft ;
2059+ } else {
2060+ // Last resort: Above element (above options box)
2061+ // Will be positioned above options box by checking overlap later
2062+ topPos = offset . top - boxDimensions . height - MARGIN ;
19922063 leftPos = offset . left ;
19932064
1994- // if bottom position not perfect, move at top above the more options box
1995- if ( elemBounds . bottom + 6 + boxDimensions . height > window . innerHeight ) {
1996- topPos = offset . top - boxDimensions . height - 34 ; // 34 is for moreOptions box height
1997- leftPos = offset . left ;
2065+ // If still goes off top, place inside element
2066+ if ( topPos < viewportTop + MARGIN ) {
2067+ topPos = offset . top + MARGIN ;
19982068 }
19992069 }
20002070 }
2001- } else {
2002- topPos = offset . top - boxDimensions . height - 6 ; // 6 for just some little space to breathe
2003- leftPos = offset . left ;
2071+ }
20042072
2005- if ( elemBounds . top - boxDimensions . height < 6 ) {
2006- // check if placing the box below would cause viewport height increase
2007- // we need this or else it might cause a flickering issue
2008- // read this to know why flickering occurs:
2009- // when we hover over the bottom part of a tall element, the info box appears below it.
2010- // this increases the live preview height, which makes the cursor position relatively
2011- // higher due to content shift. the cursor then moves out of the element boundary,
2012- // ending the hover state. this makes the info box disappear, decreasing the height
2013- // back, causing the cursor to fall back into the element, restarting the hover cycle.
2014- // this creates a continuous flickering loop.
2015- const bottomPosition = offset . top + elemBounds . height + 6 ;
2016- const wouldIncreaseViewportHeight = bottomPosition + boxDimensions . height > window . innerHeight ;
2017-
2018- // we only need to use floating position during hover mode (not on click mode)
2019- const isHoverMode = shouldShowHighlightOnHover ( ) ;
2020- const shouldUseFloatingPosition = wouldIncreaseViewportHeight && isHoverMode ;
2021-
2022- if ( shouldUseFloatingPosition ) {
2023- // float over element at bottom- right to prevent layout shift during hover
2024- topPos = offset . top + elemBounds . height - boxDimensions . height - 6 ;
2025- leftPos = offset . left + elemBounds . width - boxDimensions . width ;
2026-
2027- // make sure it doesn't go off-screen
2028- if ( leftPos < 0 ) {
2029- leftPos = offset . left ; // align to left edge of element
2030- }
2031- if ( topPos < 0 ) {
2032- topPos = offset . top + 6 ; // for the top of element
2033- }
2034- } else {
2035- topPos = bottomPosition ;
2073+ // Handle overlap with options box
2074+ if ( overlap && _nodeMoreOptionsBox && _nodeMoreOptionsBox . _shadow ) {
2075+ const optionsBox = _nodeMoreOptionsBox . _shadow . querySelector ( '.phoenix-more-options-box' ) ;
2076+ if ( optionsBox ) {
2077+ const optionsRect = optionsBox . getBoundingClientRect ( ) ;
2078+ const optionsOffset = _screenOffset ( optionsBox ) ;
2079+
2080+ // Check if we overlap
2081+ const infoBox = {
2082+ left : leftPos ,
2083+ top : topPos ,
2084+ right : leftPos + boxDimensions . width ,
2085+ bottom : topPos + boxDimensions . height
2086+ } ;
2087+
2088+ const moreOptionsBox = {
2089+ left : optionsOffset . left ,
2090+ top : optionsOffset . top ,
2091+ right : optionsOffset . left + optionsRect . width ,
2092+ bottom : optionsOffset . top + optionsRect . height
2093+ } ;
2094+
2095+ const isOverlapping = ! ( infoBox . right < moreOptionsBox . left ||
2096+ moreOptionsBox . right < infoBox . left ||
2097+ infoBox . bottom < moreOptionsBox . top ||
2098+ moreOptionsBox . bottom < infoBox . top ) ;
2099+
2100+ if ( isOverlapping ) {
2101+ // Stack vertically below options box
2102+ topPos = moreOptionsBox . bottom + MARGIN ;
2103+ leftPos = offset . left ;
20362104 }
20372105 }
2106+ }
20382107
2039- // Check if the box would go off the right of the viewport
2040- if ( leftPos + boxDimensions . width > window . innerWidth ) {
2041- leftPos = window . innerWidth - boxDimensions . width - 10 ;
2042- }
2108+ // Handle horizontal viewport boundaries
2109+ if ( leftPos + boxDimensions . width > viewportRight - MARGIN ) {
2110+ const overflow = ( leftPos + boxDimensions . width ) - ( viewportRight - MARGIN ) ;
2111+ leftPos -= overflow ;
20432112 }
20442113
2045- return { topPos : topPos , leftPos : leftPos } ;
2114+ if ( leftPos < viewportLeft + MARGIN ) {
2115+ leftPos = viewportLeft + MARGIN ;
2116+ }
2117+
2118+ return { topPos, leftPos} ;
20462119 } ,
20472120
20482121 _style : function ( ) {
0 commit comments