Skip to content

Commit d604a2d

Browse files
authored
Add support for macOS legacy scroller system setting. (#262)
* Add support for macOS legacy scroller system setting. * Add missing comment. Whitespace cleanup * Add locals * Fixed ] in comment
1 parent 08a90a0 commit d604a2d

File tree

9 files changed

+94
-17
lines changed

9 files changed

+94
-17
lines changed

Libraries/Components/ScrollView/ScrollView.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,7 @@ export type Props = $ReadOnly<{|
569569
|}>;
570570

571571
type State = {|
572+
contentKey: number, // TODO(macOS ISS#2323203)
572573
layoutHeight: ?number,
573574
...ScrollResponderState,
574575
|};
@@ -683,6 +684,7 @@ class ScrollView extends React.Component<Props, State> {
683684
_headerLayoutYs: Map<string, number> = new Map();
684685

685686
state = {
687+
contentKey: 1, // TODO(macOS ISS#2323203)
686688
layoutHeight: null,
687689
...ScrollResponder.Mixin.scrollResponderMixinGetInitialState(),
688690
};
@@ -956,6 +958,10 @@ class ScrollView extends React.Component<Props, State> {
956958
x: Math.max(0, Math.min(maxX, newOffset.x)),
957959
y: Math.max(0, Math.min(maxY, newOffset.y)),
958960
});
961+
};
962+
963+
_handlePreferredScrollerStyleDidChange = (event: ScrollEvent) => {
964+
this.setState({contentKey: this.state.contentKey + 1});
959965
}; // ]TODO(macOS ISS#2323203)
960966

961967
_handleScroll = (e: ScrollEvent) => {
@@ -1107,6 +1113,7 @@ class ScrollView extends React.Component<Props, State> {
11071113
? false
11081114
: this.props.removeClippedSubviews
11091115
}
1116+
key={this.state.contentKey} // TODO(macOS ISS#2323203)
11101117
collapsable={false}>
11111118
{children}
11121119
</ScrollContentContainerViewClass>
@@ -1138,6 +1145,8 @@ class ScrollView extends React.Component<Props, State> {
11381145
// bubble up from TextInputs
11391146
onContentSizeChange: null,
11401147
onScrollKeyDown: this._handleKeyDown, // TODO(macOS ISS#2323203)
1148+
onPreferredScrollerStyleDidChange: this
1149+
._handlePreferredScrollerStyleDidChange, // TODO(macOS ISS#2323203)
11411150
onLayout: this._handleLayout,
11421151
onMomentumScrollBegin: this._scrollResponder
11431152
.scrollResponderHandleMomentumScrollBegin,

Libraries/Components/View/ViewPropTypes.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,19 @@ type DirectEventProps = $ReadOnly<{|
5757
*/
5858
onDoubleClick?: ?(event: SyntheticEvent<{}>) => mixed, // TODO(macOS ISS#2323203)
5959

60+
/**
61+
* This event is fired when the system's preferred scroller style changes.
62+
* The `preferredScrollerStyle` key will be `legacy` or `overlay`.
63+
*/
64+
onPreferredScrollerStyleDidChange?: ?(event: ScrollEvent) => mixed, // TODO(macOS ISS#2323203)
65+
6066
/**
6167
* When `acceptsKeyboardFocus` is true, the system will try to invoke this function
6268
* when the user performs accessibility key down gesture.
6369
*/
6470
onScrollKeyDown?: ?(event: ScrollEvent) => mixed, // TODO(macOS ISS#2323203)
6571

66-
onMouseEnter?: (event: SyntheticEvent<{}>) => mixed, // [TODO(macOS ISS#2323203)
72+
onMouseEnter?: (event: SyntheticEvent<{}>) => mixed, // TODO(macOS ISS#2323203)
6773

6874
/**
6975
* Invoked on mount and layout changes with:

Libraries/Lists/VirtualizedList.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,7 +1104,9 @@ class VirtualizedList extends React.PureComponent<Props, State> {
11041104
keyEventHandler = this.props.enableSelectionOnKeyPress
11051105
? this._handleKeyDown
11061106
: null;
1107-
} // ]TODO(macOS ISS#2323203)
1107+
}
1108+
const preferredScrollerStyleDidChangeHandler = this.props
1109+
.onPreferredScrollerStyleDidChange; // ]TODO(macOS ISS#2323203)
11081110
const onRefresh = props.onRefresh;
11091111
if (this._isNestedWithSameOrientation()) {
11101112
// $FlowFixMe - Typing ReactNativeComponent revealed errors
@@ -1121,6 +1123,9 @@ class VirtualizedList extends React.PureComponent<Props, State> {
11211123
<ScrollView
11221124
{...props}
11231125
onScrollKeyDown={keyEventHandler} // TODO(macOS ISS#2323203)
1126+
onPreferredScrollerStyleDidChange={
1127+
preferredScrollerStyleDidChangeHandler
1128+
} // TODO(macOS ISS#2323203)
11241129
refreshControl={
11251130
props.refreshControl == null ? (
11261131
<RefreshControl
@@ -1135,11 +1140,18 @@ class VirtualizedList extends React.PureComponent<Props, State> {
11351140
/>
11361141
);
11371142
} else {
1138-
// $FlowFixMe Invalid prop usage
1139-
return <ScrollView {...props} onScrollKeyDown={keyEventHandler} />; // TODO(macOS ISS#2323203)
1143+
return (
1144+
// $FlowFixMe Invalid prop usage
1145+
<ScrollView
1146+
{...props}
1147+
onScrollKeyDown={keyEventHandler}
1148+
onPreferredScrollerStyleDidChange={
1149+
preferredScrollerStyleDidChangeHandler
1150+
}
1151+
/>
1152+
); // TODO(macOS ISS#2323203)
11401153
}
11411154
};
1142-
11431155
_onCellLayout(e, cellKey, index) {
11441156
const layout = e.nativeEvent.layout;
11451157
const next = {

Libraries/Lists/VirtualizedSectionList.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,12 +345,17 @@ class VirtualizedSectionList<
345345
keyEventHandler = this.props.enableSelectionOnKeyPress
346346
? this._handleKeyDown
347347
: null;
348-
} // ]TODO(macOS ISS#2323203)
348+
}
349+
const preferredScrollerStyleDidChangeHandler = this.props
350+
.onPreferredScrollerStyleDidChange; // ]TODO(macOS ISS#2323203)
349351
return (
350352
<VirtualizedList
351353
{...this.state.childProps}
352354
ref={this._captureRef}
353355
onScrollKeyDown={keyEventHandler}
356+
onPreferredScrollerStyleDidChange={
357+
preferredScrollerStyleDidChangeHandler
358+
}
354359
{...this.state.selectedRowIndexPath}
355360
/> // TODO(macOS ISS#2323203)
356361
);

Libraries/Types/CoreEventTypes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ export type ScrollEvent = SyntheticEvent<
133133
zoomScale?: number,
134134
responderIgnoreScroll?: boolean,
135135
key?: string, // TODO(macOS)
136+
preferredScrollerStyle?: string, // TODO(macOS)
136137
|}>,
137138
>;
138139

React/Views/ScrollView/RCTScrollContentView.m

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,34 @@ @implementation RCTScrollContentView
1616

1717
- (void)reactSetFrame:(CGRect)frame
1818
{
19-
[super reactSetFrame:frame];
20-
2119
#if !TARGET_OS_OSX // TODO(macOS ISS#2323203)
2220
RCTScrollView *scrollView = (RCTScrollView *)self.superview.superview;
2321
#else // [TODO(macOS ISS#2323203)
2422
// macOS also has a NSClipView in its hierarchy
2523
RCTScrollView *scrollView = (RCTScrollView *)self.superview.superview.superview;
24+
25+
if (scrollView != nil) {
26+
// On macOS scroll indicators may float over the content view like they do in iOS
27+
// or depending on system preferences they may be outside of the content view
28+
// which means the clip view will be smaller than the scroll view itself.
29+
// In such cases the content view layout must shrink accordingly otherwise
30+
// the contents will overflow causing the scroll indicators to appear unnecessarily.
31+
NSScrollView *platformScrollView = scrollView.scrollView;
32+
if (platformScrollView.scrollerStyle == NSScrollerStyleLegacy) {
33+
NSScroller *verticalScroller = platformScrollView.verticalScroller;
34+
if (!verticalScroller.isHidden) {
35+
frame.size.width -= verticalScroller.frame.size.width;
36+
}
37+
NSScroller *horizontalScroller = platformScrollView.horizontalScroller;
38+
if (!horizontalScroller.isHidden) {
39+
frame.size.height -= horizontalScroller.frame.size.height;
40+
}
41+
}
42+
}
2643
#endif // ]TODO(macOS ISS#2323203)
2744

45+
[super reactSetFrame:frame];
46+
2847
if (!scrollView) {
2948
return;
3049
}

React/Views/ScrollView/RCTScrollView.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
@property (nonatomic, copy) RCTDirectEventBlock onMomentumScrollBegin;
6868
@property (nonatomic, copy) RCTDirectEventBlock onMomentumScrollEnd;
6969
@property (nonatomic, copy) RCTDirectEventBlock onScrollKeyDown; // TODO(macOS ISS#2323203)
70+
@property (nonatomic, copy) RCTDirectEventBlock onPreferredScrollerStyleDidChange; // TODO(macOS ISS#2323203)
7071

7172
- (void)flashScrollIndicators; // TODO(macOS ISS#2323203)
7273

React/Views/ScrollView/RCTScrollView.m

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ - (instancetype)initWithFrame:(CGRect)frame
187187
self.scrollEnabled = YES;
188188
self.hasHorizontalScroller = YES;
189189
self.hasVerticalScroller = YES;
190+
self.autohidesScrollers = YES;
190191
self.panGestureRecognizer = [[NSPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleCustomPan:)];
191192
#else // ]TODO(macOS ISS#2323203)
192193
[self.panGestureRecognizer addTarget:self action:@selector(handleCustomPan:)];
@@ -756,19 +757,28 @@ - (void)updateClippedSubviews
756757
- (void)viewDidMoveToWindow
757758
{
758759
[super viewDidMoveToWindow];
759-
760+
761+
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
760762
if ([self window] == nil) {
761763
// Unregister for bounds change notifications
762-
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSViewBoundsDidChangeNotification object:_scrollView.contentView];
763-
}
764-
else {
764+
[defaultCenter removeObserver:self
765+
name:NSViewBoundsDidChangeNotification
766+
object:_scrollView.contentView];
767+
[defaultCenter removeObserver:self
768+
name:NSPreferredScrollerStyleDidChangeNotification
769+
object:nil];
770+
} else {
765771
// Register for bounds change notifications so we can track scrolling
766-
[[NSNotificationCenter defaultCenter] addObserver:self
767-
selector:@selector(scrollViewDocumentViewBoundsDidChange:)
768-
name:NSViewBoundsDidChangeNotification
769-
object:_scrollView.contentView]; // NSClipView
772+
[defaultCenter addObserver:self
773+
selector:@selector(scrollViewDocumentViewBoundsDidChange:)
774+
name:NSViewBoundsDidChangeNotification
775+
object:_scrollView.contentView]; // NSClipView
776+
[defaultCenter addObserver:self
777+
selector:@selector(preferredScrollerStyleDidChange:)
778+
name:NSPreferredScrollerStyleDidChangeNotification
779+
object:nil];
770780
}
771-
781+
772782
_notifyDidScroll = ([self window] != nil);
773783
}
774784
#endif // ]TODO(macOS ISS#2323203)
@@ -1426,6 +1436,19 @@ - (void)keyDown:(UIEvent*)theEvent
14261436
}
14271437
}
14281438
}
1439+
1440+
static NSString *RCTStringForScrollerStyle(NSScrollerStyle scrollerStyle) {
1441+
switch (scrollerStyle) {
1442+
case NSScrollerStyleLegacy:
1443+
return @"legacy";
1444+
case NSScrollerStyleOverlay:
1445+
return @"overlay";
1446+
}
1447+
}
1448+
1449+
- (void)preferredScrollerStyleDidChange:(__unused NSNotification *)notification {
1450+
RCT_SEND_SCROLL_EVENT(onPreferredScrollerStyleDidChange, (@{ @"preferredScrollerStyle": RCTStringForScrollerStyle([NSScroller preferredScrollerStyle])}));
1451+
}
14291452
#endif // ]TODO(macOS ISS#2323203)
14301453

14311454
// Note: setting several properties of UIScrollView has the effect of

React/Views/ScrollView/RCTScrollViewManager.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ - (RCTPlatformView *)view
101101
RCT_EXPORT_VIEW_PROPERTY(onMomentumScrollEnd, RCTDirectEventBlock)
102102
RCT_EXPORT_VIEW_PROPERTY(DEPRECATED_sendUpdatedChildFrames, BOOL)
103103
RCT_EXPORT_OSX_VIEW_PROPERTY(onScrollKeyDown, RCTDirectEventBlock) // TODO(macOS ISS#2323203)
104+
RCT_EXPORT_OSX_VIEW_PROPERTY(onPreferredScrollerStyleDidChange, RCTDirectEventBlock) // TODO(macOS ISS#2323203)
104105
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
105106
RCT_EXPORT_VIEW_PROPERTY(contentInsetAdjustmentBehavior, UIScrollViewContentInsetAdjustmentBehavior)
106107
#endif

0 commit comments

Comments
 (0)