Skip to content

Commit 26d1480

Browse files
NickGerlemanfacebook-github-bot
authored andcommitted
Let ScrollView Know About Keyboard Opened Before Mount
Summary: ScrollView has special behavior when the keyboard is open, but starts listening to keyboard events on mount. This means a ScrollView mounted after the keyboard is already up (e.g. for a typeahead) is not initialized to the keyboard being up. This change adds `Keyboard.isVisible()` and `Keyboard.metrics()` APIs to allow seeding initial keyboard metrics. Changelog: [General][Fixed] - Inform ScrollView of Keyboard Events Before Mount Reviewed By: JoshuaGross, yungsters Differential Revision: D38701976 fbshipit-source-id: 42b354718fbf5001ca4b90de0442eeab0be91e7a
1 parent 9e4114a commit 26d1480

File tree

3 files changed

+42
-21
lines changed

3 files changed

+42
-21
lines changed

Libraries/Components/Keyboard/Keyboard.js

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export type KeyboardEventEasing =
2424
| 'linear'
2525
| 'keyboard';
2626

27-
export type KeyboardEventCoordinates = $ReadOnly<{|
27+
export type KeyboardMetrics = $ReadOnly<{|
2828
screenX: number,
2929
screenY: number,
3030
width: number,
@@ -36,7 +36,7 @@ export type KeyboardEvent = AndroidKeyboardEvent | IOSKeyboardEvent;
3636
type BaseKeyboardEvent = {|
3737
duration: number,
3838
easing: KeyboardEventEasing,
39-
endCoordinates: KeyboardEventCoordinates,
39+
endCoordinates: KeyboardMetrics,
4040
|};
4141

4242
export type AndroidKeyboardEvent = $ReadOnly<{|
@@ -47,7 +47,7 @@ export type AndroidKeyboardEvent = $ReadOnly<{|
4747

4848
export type IOSKeyboardEvent = $ReadOnly<{|
4949
...BaseKeyboardEvent,
50-
startCoordinates: KeyboardEventCoordinates,
50+
startCoordinates: KeyboardMetrics,
5151
isEventFromThisApp: boolean,
5252
|}>;
5353

@@ -103,13 +103,24 @@ type KeyboardEventDefinitions = {
103103
*/
104104

105105
class Keyboard {
106+
_currentlyShowing: ?KeyboardEvent;
107+
106108
_emitter: NativeEventEmitter<KeyboardEventDefinitions> =
107109
new NativeEventEmitter(
108110
// T88715063: NativeEventEmitter only used this parameter on iOS. Now it uses it on all platforms, so this code was modified automatically to preserve its behavior
109111
// If you want to use the native module on other platforms, please remove this condition and test its behavior
110112
Platform.OS !== 'ios' ? null : NativeKeyboardObserver,
111113
);
112114

115+
constructor() {
116+
this.addListener('keyboardDidShow', ev => {
117+
this._currentlyShowing = ev;
118+
});
119+
this.addListener('keyboardDidHide', _ev => {
120+
this._currentlyShowing = null;
121+
});
122+
}
123+
113124
/**
114125
* The `addListener` function connects a JavaScript function to an identified native
115126
* keyboard notification event.
@@ -158,6 +169,20 @@ class Keyboard {
158169
dismissKeyboard();
159170
}
160171

172+
/**
173+
* Whether the keyboard is last known to be visible.
174+
*/
175+
isVisible(): boolean {
176+
return !!this._currentlyShowing;
177+
}
178+
179+
/**
180+
* Return the metrics of the soft-keyboard if visible.
181+
*/
182+
metrics(): ?KeyboardMetrics {
183+
return this._currentlyShowing?.endCoordinates;
184+
}
185+
161186
/**
162187
* Useful for syncing TextInput (or other keyboard accessory view) size of
163188
* position changes with keyboard movements.

Libraries/Components/Keyboard/KeyboardAvoidingView.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import type {
2222
ViewLayout,
2323
ViewLayoutEvent,
2424
} from '../View/ViewPropTypes';
25-
import type {KeyboardEvent, KeyboardEventCoordinates} from './Keyboard';
25+
import type {KeyboardEvent, KeyboardMetrics} from './Keyboard';
2626

2727
type Props = $ReadOnly<{|
2828
...ViewProps,
@@ -71,7 +71,7 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
7171
this.viewRef = React.createRef();
7272
}
7373

74-
_relativeKeyboardHeight(keyboardFrame: KeyboardEventCoordinates): number {
74+
_relativeKeyboardHeight(keyboardFrame: KeyboardMetrics): number {
7575
const frame = this._frame;
7676
if (!frame || !keyboardFrame) {
7777
return 0;

Libraries/Components/ScrollView/ScrollView.js

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
4242
import type {ViewProps} from '../View/ViewPropTypes';
4343
import ScrollViewContext, {HORIZONTAL, VERTICAL} from './ScrollViewContext';
4444
import type {Props as ScrollViewStickyHeaderProps} from './ScrollViewStickyHeader';
45-
import type {KeyboardEvent} from '../Keyboard/Keyboard';
45+
import type {KeyboardEvent, KeyboardMetrics} from '../Keyboard/Keyboard';
4646
import type {EventSubscription} from '../../vendor/emitter/EventEmitter';
4747

4848
import Commands from './ScrollViewCommands';
@@ -731,7 +731,7 @@ class ScrollView extends React.Component<Props, State> {
731731
new Map();
732732
_headerLayoutYs: Map<string, number> = new Map();
733733

734-
_keyboardWillOpenTo: ?KeyboardEvent = null;
734+
_keyboardMetrics: ?KeyboardMetrics = null;
735735
_additionalScrollOffset: number = 0;
736736
_isTouching: boolean = false;
737737
_lastMomentumScrollBeginTime: number = 0;
@@ -769,7 +769,7 @@ class ScrollView extends React.Component<Props, State> {
769769
);
770770
}
771771

772-
this._keyboardWillOpenTo = null;
772+
this._keyboardMetrics = Keyboard.metrics();
773773
this._additionalScrollOffset = 0;
774774

775775
this._subscriptionKeyboardWillShow = Keyboard.addListener(
@@ -1075,8 +1075,8 @@ class ScrollView extends React.Component<Props, State> {
10751075
let keyboardScreenY = Dimensions.get('window').height;
10761076

10771077
const scrollTextInputIntoVisibleRect = () => {
1078-
if (this._keyboardWillOpenTo != null) {
1079-
keyboardScreenY = this._keyboardWillOpenTo.endCoordinates.screenY;
1078+
if (this._keyboardMetrics != null) {
1079+
keyboardScreenY = this._keyboardMetrics.screenY;
10801080
}
10811081
let scrollOffsetY =
10821082
top - keyboardScreenY + height + this._additionalScrollOffset;
@@ -1094,8 +1094,8 @@ class ScrollView extends React.Component<Props, State> {
10941094
this._preventNegativeScrollOffset = false;
10951095
};
10961096

1097-
if (this._keyboardWillOpenTo == null) {
1098-
// `_keyboardWillOpenTo` is set inside `scrollResponderKeyboardWillShow` which
1097+
if (this._keyboardMetrics == null) {
1098+
// `_keyboardMetrics` is set inside `scrollResponderKeyboardWillShow` which
10991099
// is not guaranteed to be called before `_inputMeasureAndScrollToKeyboard` but native has already scheduled it.
11001100
// In case it was not called before `_inputMeasureAndScrollToKeyboard`, we postpone scrolling to
11011101
// text input.
@@ -1243,32 +1243,28 @@ class ScrollView extends React.Component<Props, State> {
12431243
scrollResponderKeyboardWillShow: (e: KeyboardEvent) => void = (
12441244
e: KeyboardEvent,
12451245
) => {
1246-
this._keyboardWillOpenTo = e;
1246+
this._keyboardMetrics = e.endCoordinates;
12471247
this.props.onKeyboardWillShow && this.props.onKeyboardWillShow(e);
12481248
};
12491249

12501250
scrollResponderKeyboardWillHide: (e: KeyboardEvent) => void = (
12511251
e: KeyboardEvent,
12521252
) => {
1253-
this._keyboardWillOpenTo = null;
1253+
this._keyboardMetrics = null;
12541254
this.props.onKeyboardWillHide && this.props.onKeyboardWillHide(e);
12551255
};
12561256

12571257
scrollResponderKeyboardDidShow: (e: KeyboardEvent) => void = (
12581258
e: KeyboardEvent,
12591259
) => {
1260-
// TODO(7693961): The event for DidShow is not available on iOS yet.
1261-
// Use the one from WillShow and do not assign.
1262-
if (e) {
1263-
this._keyboardWillOpenTo = e;
1264-
}
1260+
this._keyboardMetrics = e.endCoordinates;
12651261
this.props.onKeyboardDidShow && this.props.onKeyboardDidShow(e);
12661262
};
12671263

12681264
scrollResponderKeyboardDidHide: (e: KeyboardEvent) => void = (
12691265
e: KeyboardEvent,
12701266
) => {
1271-
this._keyboardWillOpenTo = null;
1267+
this._keyboardMetrics = null;
12721268
this.props.onKeyboardDidHide && this.props.onKeyboardDidHide(e);
12731269
};
12741270

@@ -1547,7 +1543,7 @@ class ScrollView extends React.Component<Props, State> {
15471543
// keyboard, except on Android where setting windowSoftInputMode to
15481544
// adjustNone leads to missing keyboard events.
15491545
const softKeyboardMayBeOpen =
1550-
this._keyboardWillOpenTo != null || Platform.OS === 'android';
1546+
this._keyboardMetrics != null || Platform.OS === 'android';
15511547

15521548
return hasFocusedTextInput && softKeyboardMayBeOpen;
15531549
};

0 commit comments

Comments
 (0)