@react-native-oh/react-native-harmony 0.72.27-9 → 0.72.28-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @flow strict-local
9
+ */
10
+
11
+ import type {ViewStyleProp} from 'react-native/Libraries/Components/Keyboard/../../StyleSheet/StyleSheet';
12
+ import type {
13
+ ViewLayout,
14
+ ViewLayoutEvent,
15
+ ViewProps,
16
+ } from 'react-native/Libraries/Components/Keyboard/../View/ViewPropTypes';
17
+ import type {KeyboardEvent, KeyboardMetrics} from 'react-native/Libraries/Components/Keyboard/Keyboard';
18
+
19
+ import LayoutAnimation from 'react-native/Libraries/Components/Keyboard/../../LayoutAnimation/LayoutAnimation';
20
+ import StyleSheet from 'react-native/Libraries/Components/Keyboard/../../StyleSheet/StyleSheet';
21
+ import Platform from '../../Utilities/Platform';
22
+ import {type EventSubscription} from 'react-native/Libraries/Components/Keyboard/../../vendor/emitter/EventEmitter';
23
+ import AccessibilityInfo from 'react-native/Libraries/Components/Keyboard/../AccessibilityInfo/AccessibilityInfo';
24
+ import View from 'react-native/Libraries/Components/Keyboard/../View/View';
25
+ import Keyboard from 'react-native/Libraries/Components/Keyboard/Keyboard';
26
+ import * as React from 'react';
27
+
28
+ type Props = $ReadOnly<{|
29
+ ...ViewProps,
30
+
31
+ /**
32
+ * Specify how to react to the presence of the keyboard.
33
+ */
34
+ behavior?: ?('height' | 'position' | 'padding'),
35
+
36
+ /**
37
+ * Style of the content container when `behavior` is 'position'.
38
+ */
39
+ contentContainerStyle?: ?ViewStyleProp,
40
+
41
+ /**
42
+ * Controls whether this `KeyboardAvoidingView` instance should take effect.
43
+ * This is useful when more than one is on the screen. Defaults to true.
44
+ */
45
+ enabled?: ?boolean,
46
+
47
+ /**
48
+ * Distance between the top of the user screen and the React Native view. This
49
+ * may be non-zero in some cases. Defaults to 0.
50
+ */
51
+ keyboardVerticalOffset?: number,
52
+ |}>;
53
+
54
+ type State = {|
55
+ bottom: number,
56
+ |};
57
+
58
+ /**
59
+ * View that moves out of the way when the keyboard appears by automatically
60
+ * adjusting its height, position, or bottom padding.
61
+ */
62
+ class KeyboardAvoidingView extends React.Component<Props, State> {
63
+ _frame: ?ViewLayout = null;
64
+ _keyboardEvent: ?KeyboardEvent = null;
65
+ _subscriptions: Array<EventSubscription> = [];
66
+ viewRef: {current: React.ElementRef<typeof View> | null, ...};
67
+ _initialFrameHeight: number = 0;
68
+
69
+ constructor(props: Props) {
70
+ super(props);
71
+ this.state = {bottom: 0};
72
+ this.viewRef = React.createRef();
73
+ }
74
+
75
+ async _relativeKeyboardHeight(
76
+ keyboardFrame: KeyboardMetrics,
77
+ ): Promise<number> {
78
+ const frame = this._frame;
79
+ if (!frame || !keyboardFrame) {
80
+ return 0;
81
+ }
82
+
83
+ // On iOS when Prefer Cross-Fade Transitions is enabled, the keyboard position
84
+ // & height is reported differently (0 instead of Y position value matching height of frame)
85
+ if (
86
+ Platform.OS === 'ios' &&
87
+ keyboardFrame.screenY === 0 &&
88
+ (await AccessibilityInfo.prefersCrossFadeTransitions())
89
+ ) {
90
+ return 0;
91
+ }
92
+
93
+ const keyboardY =
94
+ keyboardFrame.screenY - (this.props.keyboardVerticalOffset ?? 0);
95
+
96
+ if (this.props.behavior === 'height') {
97
+ return Math.max(
98
+ this.state.bottom + frame.y + frame.height - keyboardY,
99
+ 0,
100
+ );
101
+ }
102
+
103
+ // Calculate the displacement needed for the view such that it
104
+ // no longer overlaps with the keyboard
105
+ return Math.max(frame.y + frame.height - keyboardY, 0);
106
+ }
107
+
108
+ _onKeyboardChange = (event: ?KeyboardEvent) => {
109
+ this._keyboardEvent = event;
110
+ // $FlowFixMe[unused-promise]
111
+ this._updateBottomIfNecessary();
112
+ };
113
+
114
+ _onLayout = async (event: ViewLayoutEvent) => {
115
+ const wasFrameNull = this._frame == null;
116
+ this._frame = event.nativeEvent.layout;
117
+ if (!this._initialFrameHeight) {
118
+ // save the initial frame height, before the keyboard is visible
119
+ this._initialFrameHeight = this._frame.height;
120
+ }
121
+
122
+ if (wasFrameNull) {
123
+ await this._updateBottomIfNecessary();
124
+ }
125
+
126
+ if (this.props.onLayout) {
127
+ this.props.onLayout(event);
128
+ }
129
+ };
130
+
131
+ _updateBottomIfNecessary = async () => {
132
+ if (this._keyboardEvent == null) {
133
+ this.setState({bottom: 0});
134
+ return;
135
+ }
136
+
137
+ const {duration, easing, endCoordinates} = this._keyboardEvent;
138
+ const height = await this._relativeKeyboardHeight(endCoordinates);
139
+
140
+ if (this.state.bottom === height) {
141
+ return;
142
+ }
143
+
144
+ if (duration && easing) {
145
+ LayoutAnimation.configureNext({
146
+ // We have to pass the duration equal to minimal accepted duration defined here: RCTLayoutAnimation.m
147
+ duration: duration > 10 ? duration : 10,
148
+ update: {
149
+ duration: duration > 10 ? duration : 10,
150
+ type: LayoutAnimation.Types[easing] || 'keyboard',
151
+ },
152
+ });
153
+ }
154
+ this.setState({bottom: height});
155
+ };
156
+
157
+ componentDidMount(): void {
158
+ if (Platform.OS === 'ios') {
159
+ this._subscriptions = [
160
+ Keyboard.addListener('keyboardWillChangeFrame', this._onKeyboardChange),
161
+ ];
162
+ } else {
163
+ this._subscriptions = [
164
+ Keyboard.addListener('keyboardDidHide', this._onKeyboardChange),
165
+ Keyboard.addListener('keyboardDidShow', this._onKeyboardChange),
166
+ ];
167
+ }
168
+ }
169
+
170
+ componentWillUnmount(): void {
171
+ this._subscriptions.forEach(subscription => {
172
+ subscription.remove();
173
+ });
174
+ }
175
+
176
+ render(): React.Node {
177
+ const {
178
+ behavior,
179
+ children,
180
+ contentContainerStyle,
181
+ enabled = true,
182
+ // eslint-disable-next-line no-unused-vars
183
+ keyboardVerticalOffset = 0,
184
+ style,
185
+ onLayout,
186
+ ...props
187
+ } = this.props;
188
+ const bottomHeight = enabled === true ? this.state.bottom : 0;
189
+ switch (behavior) {
190
+ case 'height':
191
+ let heightStyle;
192
+ if (this._frame != null && this.state.bottom > 0) {
193
+ // Note that we only apply a height change when there is keyboard present,
194
+ // i.e. this.state.bottom is greater than 0. If we remove that condition,
195
+ // this.frame.height will never go back to its original value.
196
+ // When height changes, we need to disable flex.
197
+ heightStyle = {
198
+ height: this._initialFrameHeight - bottomHeight,
199
+ flex: 0,
200
+ };
201
+ }
202
+ /* RNOH: patch */
203
+ return (
204
+ <View
205
+ ref={this.viewRef}
206
+ style={StyleSheet.compose(style, heightStyle)}
207
+ onLayout={this._onLayout}
208
+ {...props}>
209
+ {React.Children.map(children, child => React.cloneElement(child, {__keyboardAvoidingViewBottomHeight: bottomHeight}))}
210
+ </View>
211
+ );
212
+
213
+ case 'position':
214
+ return (
215
+ <View
216
+ ref={this.viewRef}
217
+ style={style}
218
+ onLayout={this._onLayout}
219
+ {...props}>
220
+ <View
221
+ style={StyleSheet.compose(contentContainerStyle, {
222
+ bottom: bottomHeight,
223
+ })}>
224
+ {children}
225
+ </View>
226
+ </View>
227
+ );
228
+
229
+ case 'padding':
230
+ /* RNOH: patch */
231
+ return (
232
+ <View
233
+ ref={this.viewRef}
234
+ style={StyleSheet.compose(style, {paddingBottom: bottomHeight})}
235
+ onLayout={this._onLayout}
236
+ {...props}>
237
+ {React.Children.map(children, child => React.cloneElement(child, {__keyboardAvoidingViewBottomHeight: bottomHeight}))}
238
+ </View>
239
+ );
240
+
241
+ default:
242
+ return (
243
+ <View
244
+ ref={this.viewRef}
245
+ onLayout={this._onLayout}
246
+ style={style}
247
+ {...props}>
248
+ {children}
249
+ </View>
250
+ );
251
+ }
252
+ }
253
+ }
254
+
255
+ export default KeyboardAvoidingView;
@@ -684,6 +684,8 @@ type State = {|
684
684
  // RNOH patch - resolving flashScrollIndicators command on the JS side
685
685
  // as it is currently not feasible on the native side
686
686
  showScrollIndicator: boolean,
687
+ contentOffset: ?number,
688
+ contentHeight: ?number,
687
689
  |};
688
690
 
689
691
  const IS_ANIMATING_TOUCH_START_THRESHOLD_MS = 16;
@@ -772,6 +774,8 @@ class ScrollView extends React.Component<Props, State> {
772
774
  // RNOH patch - resolving flashScrollIndicators command on the JS side
773
775
  // as it is currently not feasible on the native side
774
776
  showScrollIndicator: false,
777
+ contentOffset: null,
778
+ contentHeight: null,
775
779
  };
776
780
 
777
781
  componentDidMount() {
@@ -1188,14 +1192,15 @@ class ScrollView extends React.Component<Props, State> {
1188
1192
  );
1189
1193
  }
1190
1194
  }
1195
+ this.setState({
1196
+ contentOffset: e.nativeEvent.contentOffset.y
1197
+ });
1191
1198
  this._observedScrollSinceBecomingResponder = true;
1192
1199
  this.props.onScroll && this.props.onScroll(e);
1193
1200
  };
1194
1201
 
1195
1202
  _handleLayout = (e: LayoutEvent) => {
1196
- if (this.props.invertStickyHeaders === true) {
1197
- this.setState({ layoutHeight: e.nativeEvent.layout.height });
1198
- }
1203
+ this.setState({ layoutHeight: e.nativeEvent.layout.height });
1199
1204
  if (this.props.onLayout) {
1200
1205
  this.props.onLayout(e);
1201
1206
  }
@@ -1203,6 +1208,26 @@ class ScrollView extends React.Component<Props, State> {
1203
1208
 
1204
1209
  _handleContentOnLayout = (e: LayoutEvent) => {
1205
1210
  const { width, height } = e.nativeEvent.layout;
1211
+ /**
1212
+ * RNOH patch - Fixed the issue where sticky headers are in disordered position after foldable phone is expanded/folded.
1213
+ * When a foldable phone is expanded/folded, the height of the list items often changes,
1214
+ * causing the content height of the ScrollView to change.
1215
+ * But since the scroll event is not triggered, the position of the sticky headers is
1216
+ * still offset according to the original content height.
1217
+ * So we should recalculate the contentOffset based on the new contentHeight and trigger
1218
+ * the scroll event to fix the position of sticky headers
1219
+ */
1220
+ let { contentOffset, contentHeight, layoutHeight } = this.state;
1221
+ if (contentOffset && contentHeight) {
1222
+ contentOffset *= (height - layoutHeight) / (contentHeight - layoutHeight);
1223
+ (contentOffset < 0) && (contentOffset = 0);
1224
+ this.scrollTo({ y: contentOffset, animated: false });
1225
+ this._scrollAnimatedValue.setValue(contentOffset);
1226
+ }
1227
+ this.setState({
1228
+ contentOffset,
1229
+ contentHeight: height
1230
+ });
1206
1231
  this.props.onContentSizeChange &&
1207
1232
  this.props.onContentSizeChange(width, height);
1208
1233
  };
@@ -406,6 +406,9 @@ class StatusBar extends React.Component<Props> {
406
406
  StatusBar._defaultProps
407
407
  );
408
408
  //RNOH: patch - delete code specific to IOS and Android
409
+ if (!oldProps || oldProps.hidden.value !== mergedProps.hidden.value) {
410
+ NativeStatusBarManagerHarmony.setHidden(mergedProps.hidden.value);
411
+ }
409
412
 
410
413
  // Update the props that have changed using the merged values from the props stack.
411
414
  if (!oldProps || oldProps.barStyle.value !== mergedProps.barStyle.value) {
@@ -431,9 +434,6 @@ class StatusBar extends React.Component<Props> {
431
434
  NativeStatusBarManagerHarmony.setColor(processedColor, mergedProps.backgroundColor.animated);
432
435
  }
433
436
  }
434
- if (!oldProps || oldProps.hidden.value !== mergedProps.hidden.value) {
435
- NativeStatusBarManagerHarmony.setHidden(mergedProps.hidden.value);
436
- }
437
437
  // Update the current prop values.
438
438
  StatusBar._currentValues = mergedProps;
439
439
  });
package/index.js CHANGED
@@ -76,7 +76,7 @@ module.exports = {
76
76
  return require('react-native/Libraries/Components/Keyboard/Keyboard');
77
77
  },
78
78
  get KeyboardAvoidingView() {
79
- return require('react-native/Libraries/Components/Keyboard/KeyboardAvoidingView')
79
+ return require('./Libraries/Components/Keyboard/KeyboardAvoidingView')
80
80
  .default;
81
81
  },
82
82
  get NativeEventEmitter() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-native-oh/react-native-harmony",
3
- "version": "0.72.27-9",
3
+ "version": "0.72.28-1",
4
4
  "description": "",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/",
@@ -49,7 +49,7 @@
49
49
  "./*.har"
50
50
  ],
51
51
  "dependencies": {
52
- "@react-native-oh/react-native-harmony-cli": "^0.0.26-9",
52
+ "@react-native-oh/react-native-harmony-cli": "^0.0.26-12",
53
53
  "colors": "^1.4.0",
54
54
  "fs-extra": "^11.1.1",
55
55
  "metro": "^0.76.3",
Binary file