Skip to content

Commit 095e8c9

Browse files
committed
fix(calculateInPlacePosition): implement verticalPosition "auto"
1 parent 53a3412 commit 095e8c9

File tree

1 file changed

+80
-9
lines changed

1 file changed

+80
-9
lines changed

ember-basic-dropdown/src/utils/calculate-position.ts

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

Comments
 (0)