Skip to content
This repository was archived by the owner on Nov 25, 2021. It is now read-only.

Commit 85c6713

Browse files
committed
fix(overlayposition): fix and test overlaypositioning in scrolling cases
1 parent 79e1c90 commit 85c6713

File tree

2 files changed

+85
-34
lines changed

2 files changed

+85
-34
lines changed

src/overlay_position.test.ts

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ describe('overlay_position', () => {
3232
background: lightgray;
3333
margin: 20px;
3434
width: 800px;
35-
height: 600px;
3635
position: relative;
3736
}
3837
.hover-overlay-element {
@@ -62,20 +61,78 @@ describe('overlay_position', () => {
6261
})
6362
afterEach(() => {
6463
relativeElement.remove()
64+
document.scrollingElement!.scrollTop = 0
6565
})
6666

67-
it('should return a position above the given target if the overlay fits above', () => {
68-
const target = createTarget({ left: 100, top: 200 })
69-
const position = calculateOverlayPosition({ relativeElement, target, hoverOverlayElement })
70-
applyOffsets(hoverOverlayElement, position)
71-
assert.deepStrictEqual(position, { left: 100, top: 50 })
67+
describe('with the scrolling element being document', () => {
68+
describe('not scrolled', () => {
69+
beforeEach(() => {
70+
relativeElement.style.height = '600px'
71+
})
72+
73+
it('should return a position above the given target if the overlay fits above', () => {
74+
const target = createTarget({ left: 100, top: 200 })
75+
const position = calculateOverlayPosition({ relativeElement, target, hoverOverlayElement })
76+
applyOffsets(hoverOverlayElement, position)
77+
assert.deepStrictEqual(position, { left: 100, top: 50 })
78+
})
79+
80+
it('should return a position below the a given target if the overlay does not fit above', () => {
81+
const target = createTarget({ left: 100, top: 100 })
82+
const position = calculateOverlayPosition({ relativeElement, target, hoverOverlayElement })
83+
applyOffsets(hoverOverlayElement, position)
84+
assert.deepStrictEqual(position, { left: 100, top: 116 })
85+
})
86+
})
87+
describe('scrolled', () => {
88+
beforeEach(() => {
89+
relativeElement.style.height = '3000px'
90+
document.scrollingElement!.scrollTop = 400
91+
})
92+
93+
it('should return a position above the given target if the overlay fits above', () => {
94+
const target = createTarget({ left: 100, top: 600 })
95+
const position = calculateOverlayPosition({ relativeElement, target, hoverOverlayElement })
96+
applyOffsets(hoverOverlayElement, position)
97+
assert.deepStrictEqual(position, { left: 100, top: 450 })
98+
})
99+
100+
it('should return a position below the a given target if the overlay does not fit above', () => {
101+
const target = createTarget({ left: 100, top: 450 })
102+
const position = calculateOverlayPosition({ relativeElement, target, hoverOverlayElement })
103+
applyOffsets(hoverOverlayElement, position)
104+
assert.deepStrictEqual(position, { left: 100, top: 466 })
105+
})
106+
})
72107
})
73108

74-
it('should return a position below the a given target if the overlay does not fit above', () => {
75-
const target = createTarget({ left: 100, top: 100 })
76-
const position = calculateOverlayPosition({ relativeElement, target, hoverOverlayElement })
77-
applyOffsets(hoverOverlayElement, position)
78-
assert.deepStrictEqual(position, { left: 100, top: 116 })
109+
describe('with the scrolling element being the relativeElement', () => {
110+
beforeEach(() => {
111+
relativeElement.style.height = '600px'
112+
relativeElement.style.overflow = 'auto'
113+
114+
const content = document.createElement('div')
115+
content.style.height = '3000px'
116+
content.style.width = '3000px'
117+
relativeElement.appendChild(content)
118+
119+
relativeElement.scrollTop = 400
120+
relativeElement.scrollLeft = 200
121+
})
122+
123+
it('should return a position above the given target if the overlay fits above', () => {
124+
const target = createTarget({ left: 300, top: 600 })
125+
const position = calculateOverlayPosition({ relativeElement, target, hoverOverlayElement })
126+
applyOffsets(hoverOverlayElement, position)
127+
assert.deepStrictEqual(position, { left: 300, top: 450 })
128+
})
129+
130+
it('should return a position below the a given target if the overlay does not fit above', () => {
131+
const target = createTarget({ left: 300, top: 450 })
132+
const position = calculateOverlayPosition({ relativeElement, target, hoverOverlayElement })
133+
applyOffsets(hoverOverlayElement, position)
134+
assert.deepStrictEqual(position, { left: 300, top: 466 })
135+
})
79136
})
80137
})
81138
})

src/overlay_position.ts

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
/**
2-
* `padding-top` of the blob element in px.
3-
* TODO find a way to remove the need for this.
4-
*/
5-
const BLOB_PADDING_TOP = 8
6-
71
interface CalculateOverlayPositionOptions {
82
/** The closest parent element that is `position: relative` */
93
relativeElement: HTMLElement
@@ -29,26 +23,26 @@ export const calculateOverlayPosition = ({
2923
target,
3024
hoverOverlayElement,
3125
}: CalculateOverlayPositionOptions): CSSOffsets => {
32-
// The scrollable element is the one with scrollbars. The scrolling element is the one with the content.
3326
const relativeElementBounds = relativeElement.getBoundingClientRect()
34-
const targetBound = target.getBoundingClientRect() // our target elements bounds
27+
const targetBounds = target.getBoundingClientRect()
28+
const hoverOverlayBounds = hoverOverlayElement.getBoundingClientRect()
3529

36-
// Anchor it horizontally, prior to rendering to account for wrapping
37-
// changes to vertical height if the tooltip is at the edge of the viewport.
38-
const relLeft = targetBound.left - relativeElementBounds.left
30+
// If the relativeElement is scrolled horizontally, we need to account for the offset (if not scrollLeft will be 0)
31+
const relativeHoverOverlayLeft = targetBounds.left + relativeElement.scrollLeft - relativeElementBounds.left
3932

40-
// Anchor the tooltip vertically.
41-
const tooltipBound = hoverOverlayElement.getBoundingClientRect()
42-
const relTop = targetBound.top + relativeElement.scrollTop - relativeElementBounds.top
43-
// This is the padding-top of the blob element
44-
let tooltipTop = relTop - (tooltipBound.height - BLOB_PADDING_TOP)
45-
if (tooltipTop - relativeElement.scrollTop < 0) {
46-
// Tooltip wouldn't be visible from the top, so display it at the
47-
// bottom.
48-
const relBottom = targetBound.bottom + relativeElement.scrollTop - relativeElementBounds.top
49-
tooltipTop = relBottom
33+
let relativeHoverOverlayTop: number
34+
// If the top of the hover overlay would be outside of the viewport (top negative)
35+
if (targetBounds.top - hoverOverlayBounds.height < 0) {
36+
// Position it below the target
37+
// If the relativeElement is scrolled, we need to account for the offset (if not scrollTop will be 0)
38+
relativeHoverOverlayTop = targetBounds.bottom - relativeElementBounds.top + relativeElement.scrollTop
5039
} else {
51-
tooltipTop -= BLOB_PADDING_TOP
40+
// Else position it above the target
41+
// Caculate the offset from the top of the relativeElement content to the top of the target
42+
// If the relativeElement is scrolled, we need to account for the offset (if not scrollTop will be 0)
43+
const relativeTargetTop = targetBounds.top - relativeElementBounds.top + relativeElement.scrollTop
44+
relativeHoverOverlayTop = relativeTargetTop - hoverOverlayBounds.height
5245
}
53-
return { left: relLeft, top: tooltipTop }
46+
47+
return { left: relativeHoverOverlayLeft, top: relativeHoverOverlayTop }
5448
}

0 commit comments

Comments
 (0)