Skip to content

Commit 82cbe9a

Browse files
committed
fix(scrollIntoView): respect scroll padding
Fixes #7037
1 parent 369e1e7 commit 82cbe9a

File tree

2 files changed

+75
-12
lines changed

2 files changed

+75
-12
lines changed

packages/@react-aria/utils/src/scrollIntoView.ts

+28-12
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,40 @@ export function scrollIntoView(scrollView: HTMLElement, element: HTMLElement) {
3030
let x = scrollView.scrollLeft;
3131
let y = scrollView.scrollTop;
3232

33-
// Account for top/left border offsetting the scroll top/Left
34-
let {borderTopWidth, borderLeftWidth} = getComputedStyle(scrollView);
35-
let borderAdjustedX = scrollView.scrollLeft + parseInt(borderLeftWidth, 10);
36-
let borderAdjustedY = scrollView.scrollTop + parseInt(borderTopWidth, 10);
33+
// Account for top/left border offsetting the scroll top/Left + scroll padding
34+
let {
35+
borderTopWidth,
36+
borderLeftWidth,
37+
scrollPaddingTop,
38+
scrollPaddingRight,
39+
scrollPaddingBottom,
40+
scrollPaddingLeft
41+
} = getComputedStyle(scrollView);
42+
43+
let borderAdjustedX = x + parseInt(borderLeftWidth, 10);
44+
let borderAdjustedY = y + parseInt(borderTopWidth, 10);
3745
// Ignore end/bottom border via clientHeight/Width instead of offsetHeight/Width
3846
let maxX = borderAdjustedX + scrollView.clientWidth;
3947
let maxY = borderAdjustedY + scrollView.clientHeight;
4048

41-
if (offsetX <= x) {
42-
x = offsetX - parseInt(borderLeftWidth, 10);
43-
} else if (offsetX + width > maxX) {
44-
x += offsetX + width - maxX;
49+
// Get scroll padding values as pixels - defaults to 0 if no scroll padding
50+
// is used.
51+
let scrollPaddingTopNumber = parseInt(scrollPaddingTop, 10) || 0;
52+
let scrollPaddingBottomNumber = parseInt(scrollPaddingBottom, 10) || 0;
53+
let scrollPaddingRightNumber = parseInt(scrollPaddingRight, 10) || 0;
54+
let scrollPaddingLeftNumber = parseInt(scrollPaddingLeft, 10) || 0;
55+
56+
if (offsetX <= x + scrollPaddingLeftNumber) {
57+
x = offsetX - parseInt(borderLeftWidth, 10) - scrollPaddingLeftNumber;
58+
} else if (offsetX + width > maxX - scrollPaddingRightNumber) {
59+
x += offsetX + width - maxX + scrollPaddingRightNumber;
4560
}
46-
if (offsetY <= borderAdjustedY) {
47-
y = offsetY - parseInt(borderTopWidth, 10);
48-
} else if (offsetY + height > maxY) {
49-
y += offsetY + height - maxY;
61+
if (offsetY <= borderAdjustedY + scrollPaddingTopNumber) {
62+
y = offsetY - parseInt(borderTopWidth, 10) - scrollPaddingTopNumber;
63+
} else if (offsetY + height > maxY - scrollPaddingBottomNumber) {
64+
y += offsetY + height - maxY + scrollPaddingBottomNumber;
5065
}
66+
5167
scrollView.scrollLeft = x;
5268
scrollView.scrollTop = y;
5369
}

packages/react-aria-components/stories/Menu.stories.tsx

+47
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,53 @@ export const MenuComplex = () => (
6868
</MenuTrigger>
6969
);
7070

71+
export const MenuScrollPaddingExample = () => (
72+
<MenuTrigger>
73+
<Button aria-label="Menu"></Button>
74+
<Popover>
75+
<Menu
76+
className={styles.menu}
77+
onAction={action('onAction')}
78+
style={{
79+
maxHeight: 200,
80+
position: 'relative',
81+
scrollPaddingTop: '25px',
82+
scrollPaddingBottom: '25px'
83+
}}>
84+
<Header
85+
style={{
86+
fontSize: '1.2em',
87+
position: 'sticky',
88+
top: 0,
89+
height: '25px',
90+
background: 'lightgray',
91+
borderBottom: '1px solid gray'
92+
}}>
93+
Section 1
94+
</Header>
95+
{Array.from({length: 30}).map((_, i) => (
96+
<MyMenuItem key={i}>Item {i + 1}</MyMenuItem>
97+
))}
98+
</Menu>
99+
{/* Menu doesn't have a footer, so have to put one outside to
100+
and position it absolutely to demo scroll padding bottom support. */}
101+
<div
102+
style={{
103+
fontSize: '1.2em',
104+
position: 'absolute',
105+
bottom: 0,
106+
left: 0,
107+
width: '100%',
108+
height: '24px', // with the border it'll be 25px
109+
borderTop: '1px solid gray',
110+
background: 'lightgray'
111+
}}>
112+
A footer
113+
</div>
114+
</Popover>
115+
</MenuTrigger>
116+
);
117+
71118
export const SubmenuExample = (args) => (
72119
<MenuTrigger>
73120
<Button aria-label="Menu"></Button>

0 commit comments

Comments
 (0)