@@ -36,6 +36,54 @@ export type CalculatePosition = (
3636 options : CalculatePositionOptions ,
3737) => CalculatePositionResult ;
3838
39+ export type GetViewDataResult = {
40+ scroll : { left : number ; top : number } ;
41+ triggerLeft : number ;
42+ triggerTop : number ;
43+ triggerWidth : number ;
44+ triggerHeight : number ;
45+ dropdownHeight : number ;
46+ dropdownWidth : number ;
47+ viewportWidth : number ;
48+ viewportBottom : number ;
49+ } ;
50+
51+ export type GetViewData = (
52+ trigger : Element ,
53+ content : HTMLElement ,
54+ ) => GetViewDataResult ;
55+
56+ export const getViewData : GetViewData = ( trigger , content ) => {
57+ const scroll = {
58+ left : window . scrollX ,
59+ top : window . scrollY ,
60+ } ;
61+ const {
62+ left : triggerLeft ,
63+ top : triggerTop ,
64+ width : triggerWidth ,
65+ height : triggerHeight ,
66+ } = trigger . getBoundingClientRect ( ) ;
67+ const { height : dropdownHeight , width : dropdownWidth } =
68+ content . getBoundingClientRect ( ) ;
69+ const viewportWidth = document . body . clientWidth || window . innerWidth ;
70+ const viewportBottom = scroll . top + window . innerHeight ;
71+ return {
72+ scroll,
73+ // The properties top and left of the trigger client rectangle need to be absolute to
74+ // the top left corner of the document as the value it's compared to is also the total
75+ // height and not only the viewport height (window client height + scroll offset).
76+ triggerLeft : triggerLeft + window . scrollX ,
77+ triggerTop : triggerTop + window . scrollY ,
78+ triggerWidth,
79+ triggerHeight,
80+ dropdownHeight,
81+ dropdownWidth,
82+ viewportWidth,
83+ viewportBottom,
84+ } ;
85+ } ;
86+
3987export const calculateWormholedPosition : CalculatePosition = (
4088 trigger ,
4189 content ,
@@ -49,13 +97,17 @@ export const calculateWormholedPosition: CalculatePosition = (
4997 } ,
5098) => {
5199 // Collect information about all the involved DOM elements
52- const scroll = { left : window . pageXOffset , top : window . pageYOffset } ;
53- let { left : triggerLeft , top : triggerTop } = trigger . getBoundingClientRect ( ) ;
54- const { width : triggerWidth , height : triggerHeight } =
55- trigger . getBoundingClientRect ( ) ;
56- const { height : dropdownHeight } = content . getBoundingClientRect ( ) ;
57- let { width : dropdownWidth } = content . getBoundingClientRect ( ) ;
58- const viewportWidth = document . body . clientWidth || window . innerWidth ;
100+ const viewData = getViewData ( trigger , content ) ;
101+ const {
102+ scroll,
103+ triggerWidth,
104+ triggerHeight,
105+ dropdownHeight,
106+ viewportWidth,
107+ viewportBottom,
108+ } = viewData ;
109+ let { triggerLeft, triggerTop, dropdownWidth } = viewData ;
110+
59111 const style : CalculatePositionResultStyle = { } ;
60112
61113 // Apply containers' offset
@@ -172,7 +224,6 @@ export const calculateWormholedPosition: CalculatePosition = (
172224 } else if ( verticalPosition === 'below' ) {
173225 style . top = triggerTopWithScroll + triggerHeight ;
174226 } else {
175- const viewportBottom = scroll . top + window . innerHeight ;
176227 const enoughRoomBelow =
177228 triggerTopWithScroll + triggerHeight + dropdownHeight < viewportBottom ;
178229 const enoughRoomAbove = triggerTop > dropdownHeight ;
@@ -239,8 +290,28 @@ export const calculateInPlacePosition: CalculatePosition = (
239290 positionData . verticalPosition = verticalPosition ;
240291 dropdownRect = dropdownRect || content . getBoundingClientRect ( ) ;
241292 positionData . style . top = - dropdownRect . height ;
242- } else {
293+ } else if ( verticalPosition === 'below' ) {
243294 positionData . verticalPosition = 'below' ;
295+ } else {
296+ // Automatically determine if there is enough space above or below
297+ const { triggerTop, triggerHeight, dropdownHeight, viewportBottom } =
298+ getViewData ( trigger , content ) ;
299+
300+ const enoughRoomBelow =
301+ triggerTop + triggerHeight + dropdownHeight < viewportBottom ;
302+ const enoughRoomAbove = triggerTop > dropdownHeight ;
303+
304+ if ( enoughRoomBelow ) {
305+ verticalPosition = 'below' ;
306+ } else if ( enoughRoomAbove ) {
307+ verticalPosition = 'above' ;
308+ dropdownRect = dropdownRect || content . getBoundingClientRect ( ) ;
309+ positionData . style . top = - dropdownRect . height ;
310+ } else {
311+ // Not enough space above or below
312+ verticalPosition = 'below' ;
313+ }
314+ positionData . verticalPosition = verticalPosition ;
244315 }
245316 return positionData ;
246317} ;
0 commit comments