Skip to content

Commit 156cdd7

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 04ff142 commit 156cdd7

File tree

12 files changed

+105
-6
lines changed

12 files changed

+105
-6
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, // TODO(macOS GH#774)
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/ScrollView/__tests__/__snapshots__/ScrollView-test.js.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ exports[`<ScrollView /> should render as expected: should deep render when not m
1616
<RCTScrollView
1717
alwaysBounceVertical={true}
1818
onContentSizeChange={null}
19+
onInvertedDidChange={[Function]}
1920
onLayout={[Function]}
2021
onMomentumScrollBegin={[Function]}
2122
onMomentumScrollEnd={[Function]}

Libraries/Components/View/ViewPropTypes.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ 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+
* @platform macos
76+
*/
77+
onInvertedDidChange?: ?() => mixed, // TODO(macOS GH#774)
78+
7379
/**
7480
* This event is fired when the system's preferred scroller style changes.
7581
* The `preferredScrollerStyle` key will be `legacy` or `overlay`.
@@ -414,6 +420,10 @@ type IOSViewProps = $ReadOnly<{|
414420
* See https://reactnative.dev/docs/view#accessibilityElementsHidden
415421
*/
416422
accessibilityElementsHidden?: ?boolean,
423+
/**
424+
* Reverses the direction of scroll. Uses native inversion on macOS and scale transforms of -1 elsewhere
425+
*/
426+
inverted?: ?boolean, // TODO(macOS GH#774)
417427

418428
onDoubleClick?: ?(event: SyntheticEvent<{}>) => mixed, // TODO(macOS GH#774)
419429

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,
@@ -1353,6 +1356,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
13531356
<ScrollView
13541357
// [TODO(macOS GH#774)
13551358
{...(props.enableSelectionOnKeyPress && keyboardNavigationProps)}
1359+
onInvertedDidChange={invertedDidChange}
13561360
onPreferredScrollerStyleDidChange={
13571361
preferredScrollerStyleDidChangeHandler
13581362
} // TODO(macOS GH#774)]
@@ -1376,6 +1380,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
13761380
<ScrollView
13771381
// [TODO(macOS GH#774)
13781382
{...(props.enableSelectionOnKeyPress && keyboardNavigationProps)}
1383+
onInvertedDidChange={invertedDidChange}
13791384
onPreferredScrollerStyleDidChange={
13801385
preferredScrollerStyleDidChangeHandler
13811386
} // 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: 15 additions & 0 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)

0 commit comments

Comments
 (0)