Skip to content

Commit aaca207

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

File tree

2 files changed

+78
-12
lines changed

2 files changed

+78
-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: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,56 @@ 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+
>
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+
>
95+
Section 1
96+
</Header>
97+
{Array.from({ length: 30 }).map((_, i) => (
98+
<MyMenuItem key={i}>Item {i + 1}</MyMenuItem>
99+
))}
100+
</Menu>
101+
{/* Menu doesn't have a footer, so have to put one outside to
102+
and position it absolutely to demo scroll padding bottom support. */}
103+
<div
104+
style={{
105+
fontSize: "1.2em",
106+
position: "absolute",
107+
bottom: 0,
108+
left: 0,
109+
width: "100%",
110+
height: "24px", // with the border it'll be 25px
111+
borderTop: "1px solid gray",
112+
background: "lightgray",
113+
}}
114+
>
115+
A footer
116+
</div>
117+
</Popover>
118+
</MenuTrigger>
119+
);
120+
71121
export const SubmenuExample = (args) => (
72122
<MenuTrigger>
73123
<Button aria-label="Menu"></Button>

0 commit comments

Comments
 (0)