Skip to content

Commit ea07b6f

Browse files
appdenchristophpurrer
authored andcommitted
Support inverted ScrollView on macOS
Allow to render a scrollView's content in inverted order which is especially helpful in messaging applications. We can't rely on -1 scale hacks on macOS because of inverse issues with trackpad/scrollwheel, dragging scrollbars, tracking hovers, etc. Hence we added 'native' support for inverted views
1 parent 10f4c5a commit ea07b6f

File tree

11 files changed

+103
-10
lines changed

11 files changed

+103
-10
lines changed

Libraries/Components/ScrollView/ScrollView.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,10 @@ export type Props = $ReadOnly<{|
499499
* ScrollView. This is usually used with inverted ScrollViews.
500500
*/
501501
invertStickyHeaders?: ?boolean,
502+
/**
503+
* Reverses the direction of scroll. Uses native inversion on macOS and scale transforms of -1 elsewhere
504+
*/
505+
inverted?: ?boolean,
502506
/**
503507
* Determines whether the keyboard gets dismissed in response to a drag.
504508
*
@@ -1192,6 +1196,11 @@ class ScrollView extends React.Component<Props, State> {
11921196
this.setState({contentKey: this.state.contentKey + 1});
11931197
}; // ]TODO(macOS GH#774)
11941198

1199+
// [TODO(macOS GH#774)
1200+
_handleInvertedDidChange = () => {
1201+
this.setState({contentKey: this.state.contentKey + 1});
1202+
}; // ]TODO(macOS GH#774)
1203+
11951204
_handleScroll = (e: ScrollEvent) => {
11961205
if (__DEV__) {
11971206
if (
@@ -1716,6 +1725,7 @@ class ScrollView extends React.Component<Props, State> {
17161725
: this.props.removeClippedSubviews
17171726
}
17181727
key={this.state.contentKey} // TODO(macOS GH#774)
1728+
inverted={this.props.inverted} // TODO(macOS GH#774)
17191729
collapsable={false}>
17201730
{children}
17211731
</NativeDirectionalScrollContentView>
@@ -1743,6 +1753,7 @@ class ScrollView extends React.Component<Props, State> {
17431753
// Override the onContentSizeChange from props, since this event can
17441754
// bubble up from TextInputs
17451755
onContentSizeChange: null,
1756+
onInvertedDidChange: this._handleInvertedDidChange, // TODO macOS GH#774
17461757
onPreferredScrollerStyleDidChange:
17471758
this._handlePreferredScrollerStyleDidChange, // TODO(macOS GH#774)
17481759
onLayout: this._handleLayout,

Libraries/Components/View/ViewPropTypes.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ type DirectEventProps = $ReadOnly<{|
7070
*/
7171
onDoubleClick?: ?(event: SyntheticEvent<{}>) => mixed, // TODO(macOS GH#774)
7272

73+
/**
74+
* This event is fired when the scrollView's inverted property changes.
75+
*/
76+
onInvertedDidChange?: ?() => mixed, // TODO(macOS GH#774)
77+
7378
/**
7479
* This event is fired when the system's preferred scroller style changes.
7580
* The `preferredScrollerStyle` key will be `legacy` or `overlay`.

Libraries/Lists/FlatList.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ type OptionalProps<ItemT> = {|
136136
initialSelectedIndex?: ?number,
137137
// ]TODO(macOS GH#774)
138138
/**
139-
* Reverses the direction of scroll. Uses scale transforms of -1.
139+
* Reverses the direction of scroll. Uses native inversion on macOS and scale transforms of -1 elsewhere
140140
*/
141141
inverted?: ?boolean,
142142
/**

Libraries/Lists/VirtualizedList.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -979,11 +979,13 @@ class VirtualizedList extends React.PureComponent<Props, State> {
979979
this.props;
980980
const {data, horizontal} = this.props;
981981
const isVirtualizationDisabled = this._isVirtualizationDisabled();
982-
const inversionStyle = this.props.inverted
983-
? horizontalOrDefault(this.props.horizontal)
984-
? styles.horizontallyInverted
985-
: styles.verticallyInverted
986-
: null;
982+
// macOS natively supports inverted lists, thus not needing an inversion style
983+
const inversionStyle =
984+
this.props.inverted && Platform.OS !== 'macos' // TODO(macOS GH#774)
985+
? horizontalOrDefault(this.props.horizontal)
986+
? styles.horizontallyInverted
987+
: styles.verticallyInverted
988+
: null;
987989
const cells = [];
988990
const stickyIndicesFromProps = new Set(this.props.stickyHeaderIndices);
989991
const stickyHeaderIndices = [];
@@ -1330,6 +1332,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
13301332
// [TODO(macOS GH#774)
13311333
const preferredScrollerStyleDidChangeHandler =
13321334
this.props.onPreferredScrollerStyleDidChange;
1335+
const invertedDidChange = this.props.onInvertedDidChange;
13331336

13341337
const keyboardNavigationProps = {
13351338
focusable: true,
@@ -1354,6 +1357,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
13541357
{...props}
13551358
// [TODO(macOS GH#774)
13561359
{...(props.enableSelectionOnKeyPress && keyboardNavigationProps)}
1360+
onInvertedDidChange={invertedDidChange}
13571361
onPreferredScrollerStyleDidChange={
13581362
preferredScrollerStyleDidChangeHandler
13591363
} // TODO(macOS GH#774)]
@@ -1377,6 +1381,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
13771381
{...props}
13781382
// [TODO(macOS GH#774)
13791383
{...(props.enableSelectionOnKeyPress && keyboardNavigationProps)}
1384+
onInvertedDidChange={invertedDidChange}
13801385
onPreferredScrollerStyleDidChange={
13811386
preferredScrollerStyleDidChangeHandler
13821387
} // TODO(macOS GH#774)]

React/Views/ScrollView/RCTScrollContentView.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,8 @@
1111

1212
@interface RCTScrollContentView : RCTView
1313

14+
#if TARGET_OS_OSX // [TODO(macOS GH#774)
15+
@property (nonatomic, assign, getter=isInverted) BOOL inverted;
16+
#endif // ]TODO(macOS GH#774)
17+
1418
@end

React/Views/ScrollView/RCTScrollContentView.m

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
#import "RCTScrollView.h"
1919

2020
@implementation RCTScrollContentView
21+
#if TARGET_OS_OSX // [TODO(macOS GH#774)
22+
- (BOOL)isFlipped
23+
{
24+
return !self.inverted;
25+
}
26+
#endif // ]TODO(macOS GH#774)
2127

2228
- (void)reactSetFrame:(CGRect)frame
2329
{

React/Views/ScrollView/RCTScrollContentViewManager.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ @implementation RCTScrollContentViewManager
1414

1515
RCT_EXPORT_MODULE()
1616

17+
RCT_EXPORT_OSX_VIEW_PROPERTY(inverted, BOOL) // TODO(macOS GH#774)
18+
1719
- (RCTScrollContentView *)view
1820
{
1921
return [RCTScrollContentView new];

React/Views/ScrollView/RCTScrollView.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@
6767
@property (nonatomic, copy) RCTDirectEventBlock onMomentumScrollEnd;
6868
@property (nonatomic, copy) RCTDirectEventBlock onPreferredScrollerStyleDidChange; // TODO(macOS GH#774)
6969

70+
@property (nonatomic, copy) RCTDirectEventBlock onInvertedDidChange; // TODO(macOS GH#774)
71+
7072
- (void)flashScrollIndicators; // TODO(macOS GH#774)
7173

7274
@end

React/Views/ScrollView/RCTScrollView.m

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ @interface RCTCustomScrollView :
4545
@property (nonatomic, assign) BOOL pinchGestureEnabled;
4646
#else // [TODO(macOS GH#774)
4747
+ (BOOL)isCompatibleWithResponsiveScrolling;
48+
@property (nonatomic, assign, getter=isInverted) BOOL inverted;
4849
@property (nonatomic, assign, getter=isScrollEnabled) BOOL scrollEnabled;
4950
@property (nonatomic, strong) NSPanGestureRecognizer *panGestureRecognizer;
5051
#endif // ]TODO(macOS GH#774)
@@ -108,6 +109,11 @@ + (BOOL)isCompatibleWithResponsiveScrolling
108109
return YES;
109110
}
110111

112+
- (BOOL)isFlipped
113+
{
114+
return !self.inverted;
115+
}
116+
111117
- (void)scrollWheel:(NSEvent *)theEvent
112118
{
113119
if (!self.scrollEnabled) {
@@ -556,6 +562,15 @@ - (void)setAccessibilityRole:(NSAccessibilityRole)accessibilityRole
556562
{
557563
[_scrollView setAccessibilityRole:accessibilityRole];
558564
}
565+
566+
- (void)setInverted:(BOOL)inverted
567+
{
568+
BOOL changed = _inverted != inverted;
569+
_inverted = inverted;
570+
if (changed && _onInvertedDidChange) {
571+
_onInvertedDidChange(@{});
572+
}
573+
}
559574
#endif // ]TODO(macOS GH#774)
560575

561576
RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame : (CGRect)frame)
@@ -1207,14 +1222,14 @@ - (void)uiManagerWillPerformMounting:(RCTUIManager *)manager
12071222
RCTUIView *subview = self.contentView.subviews[ii]; // TODO(OSS Candidate ISS#2710739) use property instead of ivar for mac and TODO(macOS ISS#3536887)
12081223
BOOL hasNewView = NO;
12091224
if (horz) {
1210-
CGFloat leftInset = self.inverted ? self->_scrollView.contentInset.right : self->_scrollView.contentInset.left;
1225+
CGFloat leftInset = self.inverted ? self->_scrollView.contentInset.left : self->_scrollView.contentInset.right;
12111226
CGFloat x = self->_scrollView.contentOffset.x + leftInset;
1212-
hasNewView = subview.frame.origin.x > x;
1227+
hasNewView = subview.frame.origin.x >= x;
12131228
} else {
12141229
CGFloat bottomInset =
1215-
self.inverted ? self->_scrollView.contentInset.top : self->_scrollView.contentInset.bottom;
1230+
self.inverted ? self->_scrollView.contentInset.bottom : self->_scrollView.contentInset.top;
12161231
CGFloat y = self->_scrollView.contentOffset.y + bottomInset;
1217-
hasNewView = subview.frame.origin.y > y;
1232+
hasNewView = subview.frame.origin.y >= y;
12181233
}
12191234
if (hasNewView || ii == self.contentView.subviews.count - 1) { // TODO(OSS Candidate ISS#2710739) use property instead of ivar for mac
12201235
self->_prevFirstVisibleFrame = subview.frame;

React/Views/ScrollView/RCTScrollViewManager.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ - (RCTPlatformView *)view // TODO(macOS GH#774)
9999
RCT_EXPORT_VIEW_PROPERTY(onScrollEndDrag, RCTDirectEventBlock)
100100
RCT_EXPORT_VIEW_PROPERTY(onMomentumScrollBegin, RCTDirectEventBlock)
101101
RCT_EXPORT_VIEW_PROPERTY(onMomentumScrollEnd, RCTDirectEventBlock)
102+
RCT_EXPORT_OSX_VIEW_PROPERTY(onInvertedDidChange, RCTDirectEventBlock) // TODO(macOS GH#774)
102103
RCT_EXPORT_OSX_VIEW_PROPERTY(onPreferredScrollerStyleDidChange, RCTDirectEventBlock) // TODO(macOS GH#774)
103104
RCT_EXPORT_VIEW_PROPERTY(inverted, BOOL)
104105
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* __IPHONE_13_0 */

0 commit comments

Comments
 (0)