Skip to content

Commit 836e42d

Browse files
Saadnajmiclaude
andcommitted
[macOS] Fix Paper ScrollView layout with legacy scrollbars
Apply paddingEnd on the ScrollView's shadow node to account for legacy scrollbar width, matching the Fabric approach. A new RCTScrollViewShadowView (defined inline in RCTScrollViewManager.m) reads +[NSScroller preferredScrollerStyle] and +scrollerWidthForControlSize: directly during layoutWithMetrics: — these are thread-safe class methods, so no localData round-trip is needed. Key changes: - Add RCTScrollViewShadowView with paddingEnd for scrollbar width - Revert RCTScrollContentShadowView to stock (only RTL fix remains) - Delete RCTScrollContentLocalData files (no longer used) - Update preferredScrollerStyleDidChange: in RCTScrollView to re-tile and trigger content size update on system preference changes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 04e8006 commit 836e42d

File tree

6 files changed

+51
-91
lines changed

6 files changed

+51
-91
lines changed

packages/react-native/React/Views/ScrollView/MacOS/RCTScrollContentLocalData.h

Lines changed: 0 additions & 27 deletions
This file was deleted.

packages/react-native/React/Views/ScrollView/MacOS/RCTScrollContentLocalData.m

Lines changed: 0 additions & 26 deletions
This file was deleted.

packages/react-native/React/Views/ScrollView/RCTScrollContentShadowView.m

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,10 @@
99

1010
#import <yoga/Yoga.h>
1111

12-
#if TARGET_OS_OSX // [macOS
13-
#import "RCTScrollContentLocalData.h"
14-
#endif // macOS]
15-
1612
#import "RCTUtils.h"
1713

1814
@implementation RCTScrollContentShadowView
1915

20-
#if TARGET_OS_OSX // [macOS
21-
- (void)setLocalData:(RCTScrollContentLocalData *)localData
22-
{
23-
RCTAssert(
24-
[localData isKindOfClass:[RCTScrollContentLocalData class]],
25-
@"Local data object for `RCTScrollContentView` must be `RCTScrollContentLocalData` instance.");
26-
27-
super.marginEnd = (YGValue){localData.verticalScrollerWidth, YGUnitPoint};
28-
super.marginBottom = (YGValue){localData.horizontalScrollerHeight, YGUnitPoint};
29-
30-
[self didSetProps:@[@"marginEnd", @"marginBottom"]];
31-
}
32-
#endif // macOS]
33-
3416
- (void)layoutWithMetrics:(RCTLayoutMetrics)layoutMetrics layoutContext:(RCTLayoutContext)layoutContext
3517
{
3618
if (layoutMetrics.layoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) {

packages/react-native/React/Views/ScrollView/RCTScrollContentView.m

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,6 @@
1010
#import <React/RCTAssert.h>
1111
#import <React/UIView+React.h>
1212

13-
#if TARGET_OS_OSX // [macOS
14-
#import <React/RCTUIManager.h>
15-
#import "RCTScrollContentLocalData.h"
16-
#endif // macOS]
17-
1813
#import "RCTScrollView.h"
1914

2015
@implementation RCTScrollContentView
@@ -44,22 +39,7 @@ - (void)reactSetFrame:(CGRect)frame
4439

4540
[scrollView updateContentSizeIfNeeded];
4641
#if TARGET_OS_OSX // [macOS
47-
// On macOS scroll indicators may float over the content view like they do in iOS
48-
// or depending on system preferences they may be outside of the content view
49-
// which means the clip view will be smaller than the scroll view itself.
50-
// In such cases the content view layout must shrink accordingly otherwise
51-
// the contents will overflow causing the scroll indicators to appear unnecessarily.
5242
NSScrollView *platformScrollView = [scrollView scrollView];
53-
if ([platformScrollView scrollerStyle] == NSScrollerStyleLegacy) {
54-
BOOL contentHasHeight = platformScrollView.contentSize.height > 0;
55-
CGFloat horizontalScrollerHeight = ([platformScrollView hasHorizontalScroller] && contentHasHeight) ? NSHeight([[platformScrollView horizontalScroller] frame]) : 0;
56-
CGFloat verticalScrollerWidth = [platformScrollView hasVerticalScroller] ? NSWidth([[platformScrollView verticalScroller] frame]) : 0;
57-
58-
RCTScrollContentLocalData *localData = [[RCTScrollContentLocalData alloc] initWithVerticalScrollerWidth:verticalScrollerWidth horizontalScrollerHeight:horizontalScrollerHeight];
59-
60-
[[[scrollView bridge] uiManager] setLocalData:localData forView:self];
61-
}
62-
6343
if ([platformScrollView accessibilityRole] == NSAccessibilityTableRole) {
6444
NSMutableArray *subViews = [[NSMutableArray alloc] initWithCapacity:[[self subviews] count]];
6545
for (NSView *view in [self subviews]) {

packages/react-native/React/Views/ScrollView/RCTScrollView.m

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1431,6 +1431,14 @@ - (void)keyUp:(NSEvent *)event {
14311431

14321432
- (void)preferredScrollerStyleDidChange:(__unused NSNotification *)notification {
14331433
RCT_SEND_SCROLL_EVENT(onPreferredScrollerStyleDidChange, (@{ @"preferredScrollerStyle": RCTStringForScrollerStyle([NSScroller preferredScrollerStyle])}));
1434+
1435+
// When the system scrollbar style changes, force the scroll view to adopt the
1436+
// new style, re-tile, and trigger a content size update. The ScrollView's
1437+
// shadow view (RCTScrollViewShadowView) will detect the new scroller style on
1438+
// the next layout pass and update its padding accordingly.
1439+
_scrollView.scrollerStyle = [NSScroller preferredScrollerStyle];
1440+
[_scrollView tile];
1441+
[self updateContentSizeIfNeeded];
14341442
}
14351443
#endif // macOS]
14361444

packages/react-native/React/Views/ScrollView/RCTScrollViewManager.m

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,42 @@
1212
#import "RCTShadowView.h"
1313
#import "RCTUIManager.h"
1414

15+
#if TARGET_OS_OSX // [macOS
16+
// Custom shadow view for ScrollView on macOS. Applies paddingEnd to account for
17+
// legacy (always-visible) scrollbar width so Yoga lays out children within the
18+
// actual visible area (clip view), not the full ScrollView frame.
19+
@interface RCTScrollViewShadowView : RCTShadowView
20+
@end
21+
22+
@implementation RCTScrollViewShadowView
23+
24+
- (void)applyScrollbarPadding
25+
{
26+
CGFloat verticalScrollerWidth = 0;
27+
if ([NSScroller preferredScrollerStyle] == NSScrollerStyleLegacy) {
28+
verticalScrollerWidth = [NSScroller scrollerWidthForControlSize:NSControlSizeRegular
29+
scrollerStyle:NSScrollerStyleLegacy];
30+
}
31+
32+
YGValue currentPaddingEnd = super.paddingEnd;
33+
BOOL needsUpdate =
34+
(currentPaddingEnd.unit != YGUnitPoint || currentPaddingEnd.value != verticalScrollerWidth);
35+
36+
if (needsUpdate) {
37+
super.paddingEnd = (YGValue){verticalScrollerWidth, YGUnitPoint};
38+
[self didSetProps:@[@"paddingEnd"]];
39+
}
40+
}
41+
42+
- (void)layoutWithMetrics:(RCTLayoutMetrics)layoutMetrics layoutContext:(RCTLayoutContext)layoutContext
43+
{
44+
[self applyScrollbarPadding];
45+
[super layoutWithMetrics:layoutMetrics layoutContext:layoutContext];
46+
}
47+
48+
@end
49+
#endif // macOS]
50+
1551
#if !TARGET_OS_OSX // [macOS]
1652
@implementation RCTConvert (UIScrollView)
1753

@@ -62,6 +98,13 @@ - (RCTPlatformView *)view // [macOS]
6298
return [[RCTScrollView alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
6399
}
64100

101+
#if TARGET_OS_OSX // [macOS
102+
- (RCTShadowView *)shadowView
103+
{
104+
return [RCTScrollViewShadowView new];
105+
}
106+
#endif // macOS]
107+
65108
RCT_EXPORT_VIEW_PROPERTY(alwaysBounceHorizontal, BOOL)
66109
RCT_EXPORT_VIEW_PROPERTY(alwaysBounceVertical, BOOL)
67110
RCT_EXPORT_NOT_OSX_VIEW_PROPERTY(bounces, BOOL) // [macOS]

0 commit comments

Comments
 (0)