@@ -110,7 +110,11 @@ function getContainerDimensions(containerNode: Element): Dimensions {
110110 let scroll : Position = { } ;
111111 let isPinchZoomedIn = ( visualViewport ?. scale ?? 1 ) > 1 ;
112112
113- if ( containerNode . tagName === 'BODY' ) {
113+ // In the case where the container is `html` or `body` and the container doesn't have something like `position: relative`,
114+ // then position absolute will be positioned relative to the viewport, also known as the `initial containing block`.
115+ // That's why we use the visual viewport instead.
116+
117+ if ( containerNode . tagName === 'BODY' || containerNode . tagName === 'HTML' ) {
114118 let documentElement = document . documentElement ;
115119 totalWidth = documentElement . clientWidth ;
116120 totalHeight = documentElement . clientHeight ;
@@ -179,10 +183,13 @@ function getDelta(
179183 let boundarySize = boundaryDimensions [ AXIS_SIZE [ axis ] ] ;
180184 // Calculate the edges of the boundary (accomodating for the boundary padding) and the edges of the overlay.
181185 // Note that these values are with respect to the visual viewport (aka 0,0 is the top left of the viewport)
182- let boundaryStartEdge = boundaryDimensions . scroll [ AXIS [ axis ] ] + padding ;
183- let boundaryEndEdge = boundarySize + boundaryDimensions . scroll [ AXIS [ axis ] ] - padding ;
184- let startEdgeOffset = offset - containerScroll + containerOffsetWithBoundary [ axis ] - boundaryDimensions [ AXIS [ axis ] ] ;
185- let endEdgeOffset = offset - containerScroll + size + containerOffsetWithBoundary [ axis ] - boundaryDimensions [ AXIS [ axis ] ] ;
186+
187+ let boundaryStartEdge = containerOffsetWithBoundary [ axis ] + boundaryDimensions . scroll [ AXIS [ axis ] ] + padding ;
188+ let boundaryEndEdge = containerOffsetWithBoundary [ axis ] + boundaryDimensions . scroll [ AXIS [ axis ] ] + boundarySize - padding ;
189+ // transformed value of the left edge of the overlay
190+ let startEdgeOffset = offset - containerScroll + boundaryDimensions . scroll [ AXIS [ axis ] ] + containerOffsetWithBoundary [ axis ] - boundaryDimensions [ AXIS [ axis ] ] ;
191+ // transformed value of the right edge of the overlay
192+ let endEdgeOffset = offset - containerScroll + size + boundaryDimensions . scroll [ AXIS [ axis ] ] + containerOffsetWithBoundary [ axis ] - boundaryDimensions [ AXIS [ axis ] ] ;
186193
187194 // If any of the overlay edges falls outside of the boundary, shift the overlay the required amount to align one of the overlay's
188195 // edges with the closest boundary edge.
@@ -234,7 +241,8 @@ function computePosition(
234241 containerOffsetWithBoundary : Offset ,
235242 isContainerPositioned : boolean ,
236243 arrowSize : number ,
237- arrowBoundaryOffset : number
244+ arrowBoundaryOffset : number ,
245+ containerDimensions : Dimensions
238246) {
239247 let { placement, crossPlacement, axis, crossAxis, size, crossSize} = placementInfo ;
240248 let position : Position = { } ;
@@ -255,9 +263,9 @@ function computePosition(
255263
256264 position [ crossAxis ] ! += crossOffset ;
257265
258- // overlay top overlapping arrow with button bottom
266+ // overlay top or left overlapping arrow with button bottom or right
259267 const minPosition = childOffset [ crossAxis ] - overlaySize [ crossSize ] + arrowSize + arrowBoundaryOffset ;
260- // overlay bottom overlapping arrow with button top
268+ // overlay bottom or right overlapping arrow with button top or left
261269 const maxPosition = childOffset [ crossAxis ] + childOffset [ crossSize ] - arrowSize - arrowBoundaryOffset ;
262270 position [ crossAxis ] = clamp ( position [ crossAxis ] ! , minPosition , maxPosition ) ;
263271
@@ -266,8 +274,8 @@ function computePosition(
266274 // If the container is positioned (non-static), then we use the container's actual
267275 // height, as `bottom` will be relative to this height. But if the container is static,
268276 // then it can only be the `document.body`, and `bottom` will be relative to _its_
269- // container, which should be as large as boundaryDimensions .
270- const containerHeight = ( isContainerPositioned ? containerOffsetWithBoundary [ size ] : boundaryDimensions [ TOTAL_SIZE [ size ] ] ) ;
277+ // container.
278+ const containerHeight = ( isContainerPositioned ? containerOffsetWithBoundary [ size ] : containerDimensions [ TOTAL_SIZE [ size ] ] ) ;
271279 position [ FLIPPED_DIRECTION [ axis ] ] = Math . floor ( containerHeight - childOffset [ axis ] + offset ) ;
272280 } else {
273281 position [ axis ] = Math . floor ( childOffset [ axis ] + childOffset [ size ] + offset ) ;
@@ -283,42 +291,55 @@ function getMaxHeight(
283291 margins : Position ,
284292 padding : number ,
285293 overlayHeight : number ,
286- heightGrowthDirection : HeightGrowthDirection
294+ heightGrowthDirection : HeightGrowthDirection ,
295+ containerDimensions : Dimensions
287296) {
288- const containerHeight = ( isContainerPositioned ? containerOffsetWithBoundary . height : boundaryDimensions [ TOTAL_SIZE . height ] ) ;
289- // For cases where position is set via "bottom" instead of "top", we need to calculate the true overlay top with respect to the boundary. Reverse calculate this with the same method
290- // used in computePosition .
291- let overlayTop = position . top != null ? containerOffsetWithBoundary . top + position . top : containerOffsetWithBoundary . top + ( containerHeight - ( position . bottom ?? 0 ) - overlayHeight ) ;
297+ const containerHeight = ( isContainerPositioned ? containerOffsetWithBoundary . height : containerDimensions [ TOTAL_SIZE . height ] ) ;
298+ // For cases where position is set via "bottom" instead of "top", we need to calculate the true overlay top
299+ // with respect to the container .
300+ let overlayTop = position . top != null ? position . top : ( containerHeight - ( position . bottom ?? 0 ) - overlayHeight ) ;
292301 let maxHeight = heightGrowthDirection !== 'top' ?
293302 // We want the distance between the top of the overlay to the bottom of the boundary
294303 Math . max ( 0 ,
295- ( boundaryDimensions . height + boundaryDimensions . top + ( boundaryDimensions . scroll . top ?? 0 ) ) // this is the bottom of the boundary
304+ ( boundaryDimensions . height + boundaryDimensions . top ) // this is the bottom of the boundary
296305 - overlayTop // this is the top of the overlay
297306 - ( ( margins . top ?? 0 ) + ( margins . bottom ?? 0 ) + padding ) // save additional space for margin and padding
298307 )
299308 // We want the distance between the bottom of the overlay to the top of the boundary
300309 : Math . max ( 0 ,
301310 ( overlayTop + overlayHeight ) // this is the bottom of the overlay
302- - ( boundaryDimensions . top + ( boundaryDimensions . scroll . top ?? 0 ) ) // this is the top of the boundary
311+ - boundaryDimensions . top // this is the top of the boundary
303312 - ( ( margins . top ?? 0 ) + ( margins . bottom ?? 0 ) + padding ) // save additional space for margin and padding
304313 ) ;
305314 return Math . min ( boundaryDimensions . height - ( padding * 2 ) , maxHeight ) ;
306315}
307316
308317function getAvailableSpace (
309- boundaryDimensions : Dimensions ,
318+ boundaryDimensions : Dimensions , // boundary
310319 containerOffsetWithBoundary : Offset ,
311- childOffset : Offset ,
312- margins : Position ,
313- padding : number ,
320+ childOffset : Offset , // trigger
321+ margins : Position , // overlay
322+ padding : number , // overlay <-> boundary
314323 placementInfo : ParsedPlacement
315324) {
316325 let { placement, axis, size} = placementInfo ;
317326 if ( placement === axis ) {
318- return Math . max ( 0 , childOffset [ axis ] - boundaryDimensions [ axis ] - ( boundaryDimensions . scroll [ axis ] ?? 0 ) + containerOffsetWithBoundary [ axis ] - ( margins [ axis ] ?? 0 ) - margins [ FLIPPED_DIRECTION [ axis ] ] - padding ) ;
327+ return Math . max ( 0 ,
328+ childOffset [ axis ] // trigger start
329+ - boundaryDimensions [ axis ] // boundary start
330+ - ( margins [ axis ] ?? 0 ) // margins usually for arrows or other decorations
331+ - margins [ FLIPPED_DIRECTION [ axis ] ]
332+ - padding ) ; // padding between overlay and boundary
319333 }
320334
321- return Math . max ( 0 , boundaryDimensions [ size ] + boundaryDimensions [ axis ] + boundaryDimensions . scroll [ axis ] - containerOffsetWithBoundary [ axis ] - childOffset [ axis ] - childOffset [ size ] - ( margins [ axis ] ?? 0 ) - margins [ FLIPPED_DIRECTION [ axis ] ] - padding ) ;
335+ return Math . max ( 0 ,
336+ boundaryDimensions [ size ]
337+ + boundaryDimensions [ axis ]
338+ - childOffset [ axis ]
339+ - childOffset [ size ]
340+ - ( margins [ axis ] ?? 0 )
341+ - margins [ FLIPPED_DIRECTION [ axis ] ]
342+ - padding ) ;
322343}
323344
324345export function calculatePositionInternal (
@@ -341,7 +362,7 @@ export function calculatePositionInternal(
341362) : PositionResult {
342363 let placementInfo = parsePlacement ( placementInput ) ;
343364 let { size, crossAxis, crossSize, placement, crossPlacement} = placementInfo ;
344- let position = computePosition ( childOffset , boundaryDimensions , overlaySize , placementInfo , offset , crossOffset , containerOffsetWithBoundary , isContainerPositioned , arrowSize , arrowBoundaryOffset ) ;
365+ let position = computePosition ( childOffset , boundaryDimensions , overlaySize , placementInfo , offset , crossOffset , containerOffsetWithBoundary , isContainerPositioned , arrowSize , arrowBoundaryOffset , containerDimensions ) ;
345366 let normalizedOffset = offset ;
346367 let space = getAvailableSpace (
347368 boundaryDimensions ,
@@ -355,7 +376,8 @@ export function calculatePositionInternal(
355376 // Check if the scroll size of the overlay is greater than the available space to determine if we need to flip
356377 if ( flip && scrollSize [ size ] > space ) {
357378 let flippedPlacementInfo = parsePlacement ( `${ FLIPPED_DIRECTION [ placement ] } ${ crossPlacement } ` as Placement ) ;
358- let flippedPosition = computePosition ( childOffset , boundaryDimensions , overlaySize , flippedPlacementInfo , offset , crossOffset , containerOffsetWithBoundary , isContainerPositioned , arrowSize , arrowBoundaryOffset ) ;
379+ let flippedPosition = computePosition ( childOffset , boundaryDimensions , overlaySize , flippedPlacementInfo , offset , crossOffset , containerOffsetWithBoundary , isContainerPositioned , arrowSize , arrowBoundaryOffset , containerDimensions ) ;
380+
359381 let flippedSpace = getAvailableSpace (
360382 boundaryDimensions ,
361383 containerOffsetWithBoundary ,
@@ -400,7 +422,8 @@ export function calculatePositionInternal(
400422 margins ,
401423 padding ,
402424 overlaySize . height ,
403- heightGrowthDirection
425+ heightGrowthDirection ,
426+ containerDimensions
404427 ) ;
405428
406429 if ( userSetMaxHeight && userSetMaxHeight < maxHeight ) {
@@ -409,7 +432,7 @@ export function calculatePositionInternal(
409432
410433 overlaySize . height = Math . min ( overlaySize . height , maxHeight ) ;
411434
412- position = computePosition ( childOffset , boundaryDimensions , overlaySize , placementInfo , normalizedOffset , crossOffset , containerOffsetWithBoundary , isContainerPositioned , arrowSize , arrowBoundaryOffset ) ;
435+ position = computePosition ( childOffset , boundaryDimensions , overlaySize , placementInfo , normalizedOffset , crossOffset , containerOffsetWithBoundary , isContainerPositioned , arrowSize , arrowBoundaryOffset , containerDimensions ) ;
413436 delta = getDelta ( crossAxis , position [ crossAxis ] ! , overlaySize [ crossSize ] , boundaryDimensions , containerDimensions , padding , containerOffsetWithBoundary ) ;
414437 position [ crossAxis ] ! += delta ;
415438
@@ -507,7 +530,7 @@ export function calculatePosition(opts: PositionOpts): PositionResult {
507530 // If the container is the HTML element wrapping the body element, the retrieved scrollTop/scrollLeft will be equal to the
508531 // body element's scroll. Set the container's scroll values to 0 since the overlay's edge position value in getDelta don't then need to be further offset
509532 // by the container scroll since they are essentially the same containing element and thus in the same coordinate system
510- let containerOffsetWithBoundary : Offset = boundaryElement . tagName === 'BODY' ? getOffset ( container , false ) : getPosition ( container , boundaryElement , false ) ;
533+ let containerOffsetWithBoundary : Offset = boundaryElement . tagName === 'BODY' ? getOffset ( container , false ) : getPosition ( boundaryElement , container , false ) ;
511534 if ( container . tagName === 'HTML' && boundaryElement . tagName === 'BODY' ) {
512535 containerDimensions . scroll . top = 0 ;
513536 containerDimensions . scroll . left = 0 ;
@@ -536,7 +559,7 @@ export function calculatePosition(opts: PositionOpts): PositionResult {
536559export function getRect ( node : Element , ignoreScale : boolean ) {
537560 let { top, left, width, height} = node . getBoundingClientRect ( ) ;
538561
539- // Use offsetWidth and offsetHeight if this is an HTML element, so that
562+ // Use offsetWidth and offsetHeight if this is an HTML element, so that
540563 // the size is not affected by scale transforms.
541564 if ( ignoreScale && node instanceof node . ownerDocument . defaultView ! . HTMLElement ) {
542565 width = node . offsetWidth ;
0 commit comments