Skip to content

Commit 993de98

Browse files
authored
fix(scrollIntoView): respect scroll padding (#7484)
Fixes #7037
1 parent 8228e4e commit 993de98

File tree

2 files changed

+76
-12
lines changed

2 files changed

+76
-12
lines changed

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

Lines changed: 28 additions & 12 deletions
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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,54 @@ 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+
paddingBottom: '25px' // needed due to absolute-positioned footer
84+
}}>
85+
<Header
86+
style={{
87+
fontSize: '1.2em',
88+
position: 'sticky',
89+
top: 0,
90+
height: '25px',
91+
background: 'lightgray',
92+
borderBottom: '1px solid gray'
93+
}}>
94+
Section 1
95+
</Header>
96+
{Array.from({length: 30}).map((_, i) => (
97+
<MyMenuItem key={i}>Item {i + 1}</MyMenuItem>
98+
))}
99+
</Menu>
100+
{/* Menu doesn't have a footer, so have to put one outside to
101+
and position it absolutely to demo scroll padding bottom support. */}
102+
<div
103+
style={{
104+
fontSize: '1.2em',
105+
position: 'absolute',
106+
bottom: 0,
107+
left: 0,
108+
width: '100%',
109+
height: '24px', // with the border it'll be 25px
110+
borderTop: '1px solid gray',
111+
background: 'lightgray'
112+
}}>
113+
A footer
114+
</div>
115+
</Popover>
116+
</MenuTrigger>
117+
);
118+
71119
export const SubmenuExample = (args) => (
72120
<MenuTrigger>
73121
<Button aria-label="Menu"></Button>

0 commit comments

Comments
 (0)