diff --git a/Libraries/Components/Keyboard/Keyboard.js b/Libraries/Components/Keyboard/Keyboard.js index da76db76eee3d2..feeb4a4ec2ce70 100644 --- a/Libraries/Components/Keyboard/Keyboard.js +++ b/Libraries/Components/Keyboard/Keyboard.js @@ -25,16 +25,19 @@ type KeyboardEventName = | 'keyboardWillChangeFrame' | 'keyboardDidChangeFrame'; -export type KeyboardEvent = {| - +duration?: number, - +easing?: string, - +endCoordinates: {| - +width: number, - +height: number, - +screenX: number, - +screenY: number, - |}, -|}; +type ScreenRect = $ReadOnly<{| + screenX: number, + screenY: number, + width: number, + height: number, +|}>; + +export type KeyboardEvent = $ReadOnly<{| + duration?: number, + easing?: string, + endCoordinates: ScreenRect, + startCoordinates?: ScreenRect, +|}>; type KeyboardEventListener = (e: KeyboardEvent) => void; diff --git a/Libraries/Components/Keyboard/KeyboardAvoidingView.js b/Libraries/Components/Keyboard/KeyboardAvoidingView.js index 790c5a3acf8fe9..104fabd0f53af5 100644 --- a/Libraries/Components/Keyboard/KeyboardAvoidingView.js +++ b/Libraries/Components/Keyboard/KeyboardAvoidingView.js @@ -10,97 +10,69 @@ 'use strict'; -const createReactClass = require('create-react-class'); const Keyboard = require('Keyboard'); const LayoutAnimation = require('LayoutAnimation'); const Platform = require('Platform'); -const PropTypes = require('prop-types'); const React = require('React'); -/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error - * found when Flow v0.54 was deployed. To see the error delete this comment and - * run Flow. */ -const TimerMixin = require('react-timer-mixin'); +const StyleSheet = require('StyleSheet'); const View = require('View'); -const ViewPropTypes = require('ViewPropTypes'); import type EmitterSubscription from 'EmitterSubscription'; import type {ViewStyleProp} from 'StyleSheet'; import type {ViewProps, ViewLayout, ViewLayoutEvent} from 'ViewPropTypes'; - -type ScreenRect = { - screenX: number, - screenY: number, - width: number, - height: number, -}; -type KeyboardChangeEvent = { - startCoordinates?: ScreenRect, - endCoordinates: ScreenRect, - duration?: number, - easing?: string, -}; +import type {KeyboardEvent} from 'Keyboard'; type Props = $ReadOnly<{| ...ViewProps, + + /** + * Specify how to react to the presence of the keyboard. + */ behavior?: ?('height' | 'position' | 'padding'), + + /** + * Style of the content container when `behavior` is 'position'. + */ contentContainerStyle?: ?ViewStyleProp, - enabled?: ?boolean, - keyboardVerticalOffset?: ?number, + + /** + * Controls whether this `KeyboardAvoidingView` instance should take effect. + * This is useful when more than one is on the screen. Defaults to true. + */ + enabled: ?boolean, + + /** + * Distance between the top of the user screen and the React Native view. This + * may be non-zero in some cases. Defaults to 0. + */ + keyboardVerticalOffset: number, |}>; +type State = {| + bottom: number, +|}; + const viewRef = 'VIEW'; /** - * This is a component to solve the common problem of views that need to move out of the way of the virtual keyboard. - * It can automatically adjust either its height, position or bottom padding based on the position of the keyboard. + * View that moves out of the way when the keyboard appears by automatically + * adjusting its height, position, or bottom padding. */ -const KeyboardAvoidingView = ((createReactClass({ - displayName: 'KeyboardAvoidingView', - mixins: [TimerMixin], - - propTypes: { - ...ViewPropTypes, - /** - * Specify how the `KeyboardAvoidingView` will react to the presence of - * the keyboard. It can adjust the height, position or bottom padding of the view - */ - behavior: PropTypes.oneOf(['height', 'position', 'padding']), - - /** - * The style of the content container(View) when behavior is 'position'. - */ - contentContainerStyle: ViewPropTypes.style, - - /** - * This is the distance between the top of the user screen and the react native view, - * may be non-zero in some use cases. The default value is 0. - */ - keyboardVerticalOffset: PropTypes.number.isRequired, - /** - * This is to allow us to manually control which KAV shuld take effect when - * having more than one KAV at the same screen - */ - enabled: PropTypes.bool.isRequired, - }, - - getDefaultProps() { - return { - enabled: true, - keyboardVerticalOffset: 0, - }; - }, - - getInitialState() { - return { - bottom: 0, - }; - }, - - subscriptions: ([]: Array), - frame: (null: ?ViewLayout), - - _relativeKeyboardHeight(keyboardFrame: ScreenRect): number { - const frame = this.frame; +class KeyboardAvoidingView extends React.Component { + static defaultProps = { + enabled: true, + keyboardVerticalOffset: 0, + }; + + _frame: ?ViewLayout = null; + _subscriptions: Array = []; + + state = { + bottom: 0, + }; + + _relativeKeyboardHeight(keyboardFrame): number { + const frame = this._frame; if (!frame || !keyboardFrame) { return 0; } @@ -110,10 +82,10 @@ const KeyboardAvoidingView = ((createReactClass({ // Calculate the displacement needed for the view such that it // no longer overlaps with the keyboard return Math.max(frame.y + frame.height - keyboardY, 0); - }, + } - _onKeyboardChange(event: ?KeyboardChangeEvent) { - if (!event) { + _onKeyboardChange = (event: ?KeyboardEvent) => { + if (event == null) { this.setState({bottom: 0}); return; } @@ -135,17 +107,13 @@ const KeyboardAvoidingView = ((createReactClass({ }); } this.setState({bottom: height}); - }, + }; - _onLayout(event: ViewLayoutEvent) { - this.frame = event.nativeEvent.layout; - }, + _onLayout = (event: ViewLayoutEvent) => { + this._frame = event.nativeEvent.layout; + }; - UNSAFE_componentWillUpdate( - nextProps: Object, - nextState: Object, - nextContext?: Object, - ): void { + UNSAFE_componentWillUpdate(nextProps: Props, nextState: State): void { if ( nextState.bottom === this.state.bottom && this.props.behavior === 'height' && @@ -155,44 +123,55 @@ const KeyboardAvoidingView = ((createReactClass({ // triggered by parent component re-rendering, no need for bottom to change. nextState.bottom = 0; } - }, + } - UNSAFE_componentWillMount() { + componentDidMount(): void { if (Platform.OS === 'ios') { - this.subscriptions = [ + this._subscriptions = [ Keyboard.addListener('keyboardWillChangeFrame', this._onKeyboardChange), ]; } else { - this.subscriptions = [ + this._subscriptions = [ Keyboard.addListener('keyboardDidHide', this._onKeyboardChange), Keyboard.addListener('keyboardDidShow', this._onKeyboardChange), ]; } - }, - - componentWillUnmount() { - this.subscriptions.forEach(sub => sub.remove()); - }, - - render(): React.Element { - // $FlowFixMe(>=0.41.0) - const {behavior, children, style, ...props} = this.props; - const bottomHeight = this.props.enabled ? this.state.bottom : 0; + } + + componentWillUnmount(): void { + this._subscriptions.forEach(subscription => { + subscription.remove(); + }); + } + + render(): React.Node { + const { + behavior, + children, + contentContainerStyle, + enabled, + keyboardVerticalOffset, // eslint-disable-line no-unused-vars + style, + ...props + } = this.props; + const bottomHeight = enabled ? this.state.bottom : 0; switch (behavior) { case 'height': let heightStyle; - if (this.frame) { + if (this._frame != null) { // Note that we only apply a height change when there is keyboard present, // i.e. this.state.bottom is greater than 0. If we remove that condition, // this.frame.height will never go back to its original value. // When height changes, we need to disable flex. - heightStyle = {height: this.frame.height - bottomHeight, flex: 0}; + heightStyle = { + height: this._frame.height - bottomHeight, + flex: 0, + }; } return ( - // $FlowFixMe - Typing ReactNativeComponent revealed errors {children} @@ -200,29 +179,26 @@ const KeyboardAvoidingView = ((createReactClass({ ); case 'position': - const positionStyle = {bottom: bottomHeight}; - const {contentContainerStyle} = this.props; - return ( - // $FlowFixMe - Typing ReactNativeComponent revealed errors - + {children} ); case 'padding': - const paddingStyle = {paddingBottom: bottomHeight}; return ( - // $FlowFixMe - Typing ReactNativeComponent revealed errors {children} @@ -231,7 +207,6 @@ const KeyboardAvoidingView = ((createReactClass({ default: return ( - // $FlowFixMe - Typing ReactNativeComponent revealed errors ); } - }, -}): any): React.ComponentType); + } +} module.exports = KeyboardAvoidingView;