From bea3bb695605ad1791b2d643c0135ab795dd6aa3 Mon Sep 17 00:00:00 2001 From: empyrical Date: Sat, 17 Nov 2018 02:35:14 -0800 Subject: [PATCH] DrawerLayoutAndroid: Convert to ES6 class (#21980) Summary: This pull requests converts `DrawerLayoutAndroid` from a `createReactClass`-based component to an ES6 class component, removing its use of `NativeMethodsMixin` and `prop-types` in the process. Unfortunately this couldn't be moved over to a full `forwardRef` to the native component, because it has the methods `openDrawer` and `closeDrawer` as part of its API, and they can't be called directly on the native component. A `createRef`-based ref to the native component can be accessed from the `nativeRef` property of a `DrawerLayoutAndroid` instance if a native ref is needed. The Flow types for callbacks have also been filled out. Pull Request resolved: https://github.com/facebook/react-native/pull/21980 Reviewed By: TheSavior Differential Revision: D12901951 Pulled By: RSNara fbshipit-source-id: d35fa5f11b1059f49b466b52abeec902db1d22f7 --- .../DrawerLayoutAndroid.android.js | 321 +++++++++++------- 1 file changed, 190 insertions(+), 131 deletions(-) diff --git a/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js b/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js index a9b00611591..e41c4e5cc4d 100644 --- a/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js +++ b/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js @@ -4,35 +4,150 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * + * @flow * @format */ 'use strict'; -const DeprecatedColorPropType = require('DeprecatedColorPropType'); -const DeprecatedViewPropTypes = require('DeprecatedViewPropTypes'); -const NativeMethodsMixin = require('NativeMethodsMixin'); const Platform = require('Platform'); -const PropTypes = require('prop-types'); const React = require('React'); const ReactNative = require('ReactNative'); const StatusBar = require('StatusBar'); const StyleSheet = require('StyleSheet'); const UIManager = require('UIManager'); const View = require('View'); +const nullthrows = require('nullthrows'); const DrawerConsts = UIManager.getViewManagerConfig('AndroidDrawerLayout') .Constants; -const createReactClass = require('create-react-class'); const dismissKeyboard = require('dismissKeyboard'); const requireNativeComponent = require('requireNativeComponent'); -const RK_DRAWER_REF = 'drawerlayout'; -const INNERVIEW_REF = 'innerView'; - const DRAWER_STATES = ['Idle', 'Dragging', 'Settling']; +import type {ViewStyleProp} from 'StyleSheet'; +import type {ColorValue} from 'StyleSheetTypes'; +import type {SyntheticEvent} from 'CoreEventTypes'; +import type { + MeasureOnSuccessCallback, + MeasureInWindowOnSuccessCallback, + MeasureLayoutOnSuccessCallback, +} from 'ReactNativeTypes'; + +type DrawerStates = 'Idle' | 'Dragging' | 'Settling'; + +type DrawerStateEvent = SyntheticEvent< + $ReadOnly<{| + drawerState: number, + |}>, +>; + +type DrawerSlideEvent = SyntheticEvent< + $ReadOnly<{| + offset: number, + |}>, +>; + +type Props = $ReadOnly<{| + /** + * Determines whether the keyboard gets dismissed in response to a drag. + * - 'none' (the default), drags do not dismiss the keyboard. + * - 'on-drag', the keyboard is dismissed when a drag begins. + */ + keyboardDismissMode?: ?('none' | 'on-drag'), + + /** + * Specifies the background color of the drawer. The default value is white. + * If you want to set the opacity of the drawer, use rgba. Example: + * + * ``` + * return ( + * + * + * ); + * ``` + */ + drawerBackgroundColor: ColorValue, + + /** + * Specifies the side of the screen from which the drawer will slide in. + */ + drawerPosition: ?number, + + /** + * Specifies the width of the drawer, more precisely the width of the view that be pulled in + * from the edge of the window. + */ + drawerWidth?: ?number, + + /** + * Specifies the lock mode of the drawer. The drawer can be locked in 3 states: + * - unlocked (default), meaning that the drawer will respond (open/close) to touch gestures. + * - locked-closed, meaning that the drawer will stay closed and not respond to gestures. + * - locked-open, meaning that the drawer will stay opened and not respond to gestures. + * The drawer may still be opened and closed programmatically (`openDrawer`/`closeDrawer`). + */ + drawerLockMode?: ?('unlocked' | 'locked-closed' | 'locked-open'), + + /** + * Function called whenever there is an interaction with the navigation view. + */ + onDrawerSlide?: ?(event: DrawerSlideEvent) => mixed, + + /** + * Function called when the drawer state has changed. The drawer can be in 3 states: + * - Idle, meaning there is no interaction with the navigation view happening at the time + * - Dragging, meaning there is currently an interaction with the navigation view + * - Settling, meaning that there was an interaction with the navigation view, and the + * navigation view is now finishing its closing or opening animation + */ + onDrawerStateChanged?: ?(state: DrawerStates) => mixed, + + /** + * Function called whenever the navigation view has been opened. + */ + onDrawerOpen?: ?() => mixed, + + /** + * Function called whenever the navigation view has been closed. + */ + onDrawerClose?: ?() => mixed, + + /** + * The navigation view that will be rendered to the side of the screen and can be pulled in. + */ + renderNavigationView: () => React.Element, + + /** + * Make the drawer take the entire screen and draw the background of the + * status bar to allow it to open over the status bar. It will only have an + * effect on API 21+. + */ + statusBarBackgroundColor?: ?ColorValue, + + children?: React.Node, + style?: ?ViewStyleProp, +|}>; + +type NativeProps = $ReadOnly<{| + ...$Diff< + Props, + $ReadOnly<{onDrawerStateChanged?: ?(state: DrawerStates) => mixed}>, + >, + onDrawerStateChanged?: ?(state: DrawerStateEvent) => mixed, +|}>; + +type State = {| + statusBarBackgroundColor: ColorValue, +|}; + +// The View that contains both the actual drawer and the main view +const AndroidDrawerLayout = ((requireNativeComponent( + 'AndroidDrawerLayout', +): any): Class>); + /** * React component that wraps the platform `DrawerLayout` (Android only). The * Drawer (typically used for navigation) is rendered with `renderNavigationView` @@ -64,109 +179,20 @@ const DRAWER_STATES = ['Idle', 'Dragging', 'Settling']; * }, * ``` */ -const DrawerLayoutAndroid = createReactClass({ - displayName: 'DrawerLayoutAndroid', - statics: { - positions: DrawerConsts.DrawerPosition, - }, +class DrawerLayoutAndroid extends React.Component { + static positions = DrawerConsts.DrawerPosition; + static defaultProps = { + drawerBackgroundColor: 'white', + }; - propTypes: { - ...DeprecatedViewPropTypes, - /** - * Determines whether the keyboard gets dismissed in response to a drag. - * - 'none' (the default), drags do not dismiss the keyboard. - * - 'on-drag', the keyboard is dismissed when a drag begins. - */ - keyboardDismissMode: PropTypes.oneOf([ - 'none', // default - 'on-drag', - ]), - /** - * Specifies the background color of the drawer. The default value is white. - * If you want to set the opacity of the drawer, use rgba. Example: - * - * ``` - * return ( - * - * - * ); - * ``` - */ - drawerBackgroundColor: DeprecatedColorPropType, - /** - * Specifies the side of the screen from which the drawer will slide in. - */ - drawerPosition: PropTypes.oneOf([ - DrawerConsts.DrawerPosition.Left, - DrawerConsts.DrawerPosition.Right, - ]), - /** - * Specifies the width of the drawer, more precisely the width of the view that be pulled in - * from the edge of the window. - */ - drawerWidth: PropTypes.number, - /** - * Specifies the lock mode of the drawer. The drawer can be locked in 3 states: - * - unlocked (default), meaning that the drawer will respond (open/close) to touch gestures. - * - locked-closed, meaning that the drawer will stay closed and not respond to gestures. - * - locked-open, meaning that the drawer will stay opened and not respond to gestures. - * The drawer may still be opened and closed programmatically (`openDrawer`/`closeDrawer`). - */ - drawerLockMode: PropTypes.oneOf([ - 'unlocked', - 'locked-closed', - 'locked-open', - ]), - /** - * Function called whenever there is an interaction with the navigation view. - */ - onDrawerSlide: PropTypes.func, - /** - * Function called when the drawer state has changed. The drawer can be in 3 states: - * - idle, meaning there is no interaction with the navigation view happening at the time - * - dragging, meaning there is currently an interaction with the navigation view - * - settling, meaning that there was an interaction with the navigation view, and the - * navigation view is now finishing its closing or opening animation - */ - onDrawerStateChanged: PropTypes.func, - /** - * Function called whenever the navigation view has been opened. - */ - onDrawerOpen: PropTypes.func, - /** - * Function called whenever the navigation view has been closed. - */ - onDrawerClose: PropTypes.func, - /** - * The navigation view that will be rendered to the side of the screen and can be pulled in. - */ - renderNavigationView: PropTypes.func.isRequired, + _nativeRef = React.createRef< + Class>, + >(); - /** - * Make the drawer take the entire screen and draw the background of the - * status bar to allow it to open over the status bar. It will only have an - * effect on API 21+. - */ - statusBarBackgroundColor: DeprecatedColorPropType, - }, + state = {statusBarBackgroundColor: null}; - mixins: [NativeMethodsMixin], - - getDefaultProps: function(): {drawerBackgroundColor: string} { - return { - drawerBackgroundColor: 'white', - }; - }, - - getInitialState: function() { - return {statusBarBackgroundColor: undefined}; - }, - - getInnerViewNode: function() { - return this.refs[INNERVIEW_REF].getInnerViewNode(); - }, - - render: function() { + render() { + const {onDrawerStateChanged, ...props} = this.props; const drawStatusBar = Platform.Version >= 21 && this.props.statusBarBackgroundColor; const drawerViewWrapper = ( @@ -184,7 +210,7 @@ const DrawerLayoutAndroid = createReactClass({ ); const childrenWrapper = ( - + {drawStatusBar && ( ); - }, + } - _onDrawerSlide: function(event) { + _onDrawerSlide = (event: DrawerSlideEvent) => { if (this.props.onDrawerSlide) { this.props.onDrawerSlide(event); } if (this.props.keyboardDismissMode === 'on-drag') { dismissKeyboard(); } - }, + }; - _onDrawerOpen: function() { + _onDrawerOpen = () => { if (this.props.onDrawerOpen) { this.props.onDrawerOpen(); } - }, + }; - _onDrawerClose: function() { + _onDrawerClose = () => { if (this.props.onDrawerClose) { this.props.onDrawerClose(); } - }, + }; - _onDrawerStateChanged: function(event) { + _onDrawerStateChanged = (event: DrawerStateEvent) => { if (this.props.onDrawerStateChanged) { this.props.onDrawerStateChanged( DRAWER_STATES[event.nativeEvent.drawerState], ); } - }, + }; /** * Opens the drawer. */ - openDrawer: function() { + openDrawer() { UIManager.dispatchViewManagerCommand( this._getDrawerLayoutHandle(), UIManager.getViewManagerConfig('AndroidDrawerLayout').Commands.openDrawer, null, ); - }, + } /** * Closes the drawer. */ - closeDrawer: function() { + closeDrawer() { UIManager.dispatchViewManagerCommand( this._getDrawerLayoutHandle(), UIManager.getViewManagerConfig('AndroidDrawerLayout').Commands .closeDrawer, null, ); - }, + } + /** * Closing and opening example * Note: To access the drawer you have to give it a ref. Refs do not work on stateless components @@ -287,10 +314,45 @@ const DrawerLayoutAndroid = createReactClass({ * ) * } */ - _getDrawerLayoutHandle: function() { - return ReactNative.findNodeHandle(this.refs[RK_DRAWER_REF]); - }, -}); + _getDrawerLayoutHandle() { + return ReactNative.findNodeHandle(this._nativeRef.current); + } + + /** + * Native methods + */ + blur() { + nullthrows(this._nativeRef.current).blur(); + } + + focus() { + nullthrows(this._nativeRef.current).focus(); + } + + measure(callback: MeasureOnSuccessCallback) { + nullthrows(this._nativeRef.current).measure(callback); + } + + measureInWindow(callback: MeasureInWindowOnSuccessCallback) { + nullthrows(this._nativeRef.current).measureInWindow(callback); + } + + measureLayout( + relativeToNativeNode: number, + onSuccess: MeasureLayoutOnSuccessCallback, + onFail?: () => void, + ) { + nullthrows(this._nativeRef.current).measureLayout( + relativeToNativeNode, + onSuccess, + onFail, + ); + } + + setNativeProps(nativeProps: Object) { + nullthrows(this._nativeRef.current).setNativeProps(nativeProps); + } +} const styles = StyleSheet.create({ base: { @@ -322,7 +384,4 @@ const styles = StyleSheet.create({ }, }); -// The View that contains both the actual drawer and the main view -const AndroidDrawerLayout = requireNativeComponent('AndroidDrawerLayout'); - module.exports = DrawerLayoutAndroid;