Skip to content

Commit ec06927

Browse files
committed
Add calculate-position logic from PR #1016
1 parent 09d71d2 commit ec06927

1 file changed

Lines changed: 80 additions & 9 deletions

File tree

src/utils/calculate-position.ts

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
3786
export 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

Comments
 (0)