@@ -34,6 +34,55 @@ export type CalculatePosition = (
3434 options : CalculatePositionOptions ,
3535) => CalculatePositionResult ;
3636
37+ type GetViewDataResult = {
38+ scroll : { left : number ; top : number } ;
39+ triggerLeft : number ;
40+ triggerTop : number ;
41+ triggerWidth : number ;
42+ triggerHeight : number ;
43+ dropdownHeight : number ;
44+ dropdownWidth : number ;
45+ viewportWidth : number ;
46+ viewportBottom : number ;
47+ } ;
48+
49+ type GetViewData = (
50+ trigger : Element ,
51+ content : HTMLElement ,
52+ ) => GetViewDataResult ;
53+
54+ const getViewData : GetViewData = ( trigger , content ) => {
55+ const scroll = {
56+ left : window . scrollX ,
57+ top : window . scrollY ,
58+ } ;
59+ const {
60+ left : triggerLeft ,
61+ top : triggerTop ,
62+ width : triggerWidth ,
63+ height : triggerHeight ,
64+ } = trigger . getBoundingClientRect ( ) ;
65+ const { height : dropdownHeight , width : dropdownWidth } =
66+ content . getBoundingClientRect ( ) ;
67+ const viewportWidth = document . body . clientWidth || window . innerWidth ;
68+ const viewportBottom = scroll . top + window . innerHeight ;
69+
70+ return {
71+ scroll,
72+ // The properties top and left of the trigger client rectangle need to be absolute to
73+ // the top left corner of the document as the value it's compared to is also the total
74+ // height and not only the viewport height (window client height + scroll offset).
75+ triggerLeft : triggerLeft + window . scrollX ,
76+ triggerTop : triggerTop + window . scrollY ,
77+ triggerWidth,
78+ triggerHeight,
79+ dropdownHeight,
80+ dropdownWidth,
81+ viewportWidth,
82+ viewportBottom,
83+ } ;
84+ } ;
85+
3786export function calculateWormholedPosition (
3887 trigger : HTMLElement ,
3988 content : HTMLElement ,
@@ -47,13 +96,16 @@ export function calculateWormholedPosition(
4796 } : CalculatePositionOptions ,
4897) : CalculatePositionResult {
4998 // Collect information about all the involved DOM elements
50- const scroll = { left : window . pageXOffset , top : window . pageYOffset } ;
51- let { left : triggerLeft , top : triggerTop } = trigger . getBoundingClientRect ( ) ;
52- const { width : triggerWidth , height : triggerHeight } =
53- trigger . getBoundingClientRect ( ) ;
54- const { height : dropdownHeight } = content . getBoundingClientRect ( ) ;
55- let { width : dropdownWidth } = content . getBoundingClientRect ( ) ;
56- const viewportWidth = document . body . clientWidth || window . innerWidth ;
99+ const viewData = getViewData ( trigger , content ) ;
100+ const {
101+ scroll,
102+ triggerWidth,
103+ triggerHeight,
104+ dropdownHeight,
105+ viewportWidth,
106+ viewportBottom,
107+ } = viewData ;
108+ let { triggerLeft, triggerTop, dropdownWidth } = viewData ;
57109 const style : CalculatePositionResultStyle = { } ;
58110
59111 // Apply containers' offset
@@ -170,7 +222,6 @@ export function calculateWormholedPosition(
170222 } else if ( verticalPosition === 'below' ) {
171223 style . top = triggerTopWithScroll + triggerHeight ;
172224 } else {
173- const viewportBottom = scroll . top + window . innerHeight ;
174225 const enoughRoomBelow =
175226 triggerTopWithScroll + triggerHeight + dropdownHeight < viewportBottom ;
176227 const enoughRoomAbove = triggerTop > dropdownHeight ;
@@ -237,8 +288,28 @@ export function calculateInPlacePosition(
237288 positionData . verticalPosition = verticalPosition ;
238289 dropdownRect = dropdownRect || content . getBoundingClientRect ( ) ;
239290 positionData . style . top = - dropdownRect . height ;
240- } else {
291+ } else if ( verticalPosition === 'below' ) {
241292 positionData . verticalPosition = 'below' ;
293+ } else {
294+ // Automatically determine if there is enough space above or below
295+ const { triggerTop, triggerHeight, dropdownHeight, viewportBottom } =
296+ getViewData ( trigger , content ) ;
297+
298+ const enoughRoomBelow =
299+ triggerTop + triggerHeight + dropdownHeight < viewportBottom ;
300+ const enoughRoomAbove = triggerTop > dropdownHeight ;
301+
302+ if ( enoughRoomBelow ) {
303+ verticalPosition = 'below' ;
304+ } else if ( enoughRoomAbove ) {
305+ verticalPosition = 'above' ;
306+ dropdownRect = dropdownRect || content . getBoundingClientRect ( ) ;
307+ positionData . style . top = - dropdownRect . height ;
308+ } else {
309+ // Not enough space above or below
310+ verticalPosition = 'below' ;
311+ }
312+ positionData . verticalPosition = verticalPosition ;
242313 }
243314 return positionData ;
244315}
0 commit comments