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
This commit is contained in:
Nick Gerleman 2022-08-18 15:05:43 -07:00 коммит произвёл Facebook GitHub Bot
Родитель 9e4114abe1
Коммит 26d148029c
3 изменённых файлов: 42 добавлений и 21 удалений

Просмотреть файл

@ -24,7 +24,7 @@ export type KeyboardEventEasing =
| 'linear'
| 'keyboard';
export type KeyboardEventCoordinates = $ReadOnly<{|
export type KeyboardMetrics = $ReadOnly<{|
screenX: number,
screenY: number,
width: number,
@ -36,7 +36,7 @@ export type KeyboardEvent = AndroidKeyboardEvent | IOSKeyboardEvent;
type BaseKeyboardEvent = {|
duration: number,
easing: KeyboardEventEasing,
endCoordinates: KeyboardEventCoordinates,
endCoordinates: KeyboardMetrics,
|};
export type AndroidKeyboardEvent = $ReadOnly<{|
@ -47,7 +47,7 @@ export type AndroidKeyboardEvent = $ReadOnly<{|
export type IOSKeyboardEvent = $ReadOnly<{|
...BaseKeyboardEvent,
startCoordinates: KeyboardEventCoordinates,
startCoordinates: KeyboardMetrics,
isEventFromThisApp: boolean,
|}>;
@ -103,6 +103,8 @@ type KeyboardEventDefinitions = {
*/
class Keyboard {
_currentlyShowing: ?KeyboardEvent;
_emitter: NativeEventEmitter<KeyboardEventDefinitions> =
new NativeEventEmitter(
// 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
@ -110,6 +112,15 @@ class Keyboard {
Platform.OS !== 'ios' ? null : NativeKeyboardObserver,
);
constructor() {
this.addListener('keyboardDidShow', ev => {
this._currentlyShowing = ev;
});
this.addListener('keyboardDidHide', _ev => {
this._currentlyShowing = null;
});
}
/**
* The `addListener` function connects a JavaScript function to an identified native
* keyboard notification event.
@ -158,6 +169,20 @@ class Keyboard {
dismissKeyboard();
}
/**
* Whether the keyboard is last known to be visible.
*/
isVisible(): boolean {
return !!this._currentlyShowing;
}
/**
* Return the metrics of the soft-keyboard if visible.
*/
metrics(): ?KeyboardMetrics {
return this._currentlyShowing?.endCoordinates;
}
/**
* Useful for syncing TextInput (or other keyboard accessory view) size of
* position changes with keyboard movements.

Просмотреть файл

@ -22,7 +22,7 @@ import type {
ViewLayout,
ViewLayoutEvent,
} from '../View/ViewPropTypes';
import type {KeyboardEvent, KeyboardEventCoordinates} from './Keyboard';
import type {KeyboardEvent, KeyboardMetrics} from './Keyboard';
type Props = $ReadOnly<{|
...ViewProps,
@ -71,7 +71,7 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
this.viewRef = React.createRef();
}
_relativeKeyboardHeight(keyboardFrame: KeyboardEventCoordinates): number {
_relativeKeyboardHeight(keyboardFrame: KeyboardMetrics): number {
const frame = this._frame;
if (!frame || !keyboardFrame) {
return 0;

Просмотреть файл

@ -42,7 +42,7 @@ import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
import type {ViewProps} from '../View/ViewPropTypes';
import ScrollViewContext, {HORIZONTAL, VERTICAL} from './ScrollViewContext';
import type {Props as ScrollViewStickyHeaderProps} from './ScrollViewStickyHeader';
import type {KeyboardEvent} from '../Keyboard/Keyboard';
import type {KeyboardEvent, KeyboardMetrics} from '../Keyboard/Keyboard';
import type {EventSubscription} from '../../vendor/emitter/EventEmitter';
import Commands from './ScrollViewCommands';
@ -731,7 +731,7 @@ class ScrollView extends React.Component<Props, State> {
new Map();
_headerLayoutYs: Map<string, number> = new Map();
_keyboardWillOpenTo: ?KeyboardEvent = null;
_keyboardMetrics: ?KeyboardMetrics = null;
_additionalScrollOffset: number = 0;
_isTouching: boolean = false;
_lastMomentumScrollBeginTime: number = 0;
@ -769,7 +769,7 @@ class ScrollView extends React.Component<Props, State> {
);
}
this._keyboardWillOpenTo = null;
this._keyboardMetrics = Keyboard.metrics();
this._additionalScrollOffset = 0;
this._subscriptionKeyboardWillShow = Keyboard.addListener(
@ -1075,8 +1075,8 @@ class ScrollView extends React.Component<Props, State> {
let keyboardScreenY = Dimensions.get('window').height;
const scrollTextInputIntoVisibleRect = () => {
if (this._keyboardWillOpenTo != null) {
keyboardScreenY = this._keyboardWillOpenTo.endCoordinates.screenY;
if (this._keyboardMetrics != null) {
keyboardScreenY = this._keyboardMetrics.screenY;
}
let scrollOffsetY =
top - keyboardScreenY + height + this._additionalScrollOffset;
@ -1094,8 +1094,8 @@ class ScrollView extends React.Component<Props, State> {
this._preventNegativeScrollOffset = false;
};
if (this._keyboardWillOpenTo == null) {
// `_keyboardWillOpenTo` is set inside `scrollResponderKeyboardWillShow` which
if (this._keyboardMetrics == null) {
// `_keyboardMetrics` is set inside `scrollResponderKeyboardWillShow` which
// is not guaranteed to be called before `_inputMeasureAndScrollToKeyboard` but native has already scheduled it.
// In case it was not called before `_inputMeasureAndScrollToKeyboard`, we postpone scrolling to
// text input.
@ -1243,32 +1243,28 @@ class ScrollView extends React.Component<Props, State> {
scrollResponderKeyboardWillShow: (e: KeyboardEvent) => void = (
e: KeyboardEvent,
) => {
this._keyboardWillOpenTo = e;
this._keyboardMetrics = e.endCoordinates;
this.props.onKeyboardWillShow && this.props.onKeyboardWillShow(e);
};
scrollResponderKeyboardWillHide: (e: KeyboardEvent) => void = (
e: KeyboardEvent,
) => {
this._keyboardWillOpenTo = null;
this._keyboardMetrics = null;
this.props.onKeyboardWillHide && this.props.onKeyboardWillHide(e);
};
scrollResponderKeyboardDidShow: (e: KeyboardEvent) => void = (
e: KeyboardEvent,
) => {
// TODO(7693961): The event for DidShow is not available on iOS yet.
// Use the one from WillShow and do not assign.
if (e) {
this._keyboardWillOpenTo = e;
}
this._keyboardMetrics = e.endCoordinates;
this.props.onKeyboardDidShow && this.props.onKeyboardDidShow(e);
};
scrollResponderKeyboardDidHide: (e: KeyboardEvent) => void = (
e: KeyboardEvent,
) => {
this._keyboardWillOpenTo = null;
this._keyboardMetrics = null;
this.props.onKeyboardDidHide && this.props.onKeyboardDidHide(e);
};
@ -1547,7 +1543,7 @@ class ScrollView extends React.Component<Props, State> {
// keyboard, except on Android where setting windowSoftInputMode to
// adjustNone leads to missing keyboard events.
const softKeyboardMayBeOpen =
this._keyboardWillOpenTo != null || Platform.OS === 'android';
this._keyboardMetrics != null || Platform.OS === 'android';
return hasFocusedTextInput && softKeyboardMayBeOpen;
};