From 25dd43e960df17d0853fe3b5c188045bfff4863a Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher Date: Mon, 1 Jul 2019 16:24:14 -0700 Subject: [PATCH] [change] Update vendor code * FlatList * SectionList * VirtualizedList * VirtualizedSectionList --- package.json | 1 + .../src/moduleMap.js | 1 - .../src/exports/SwipeableFlatList/index.js | 12 - packages/react-native-web/src/index.js | 2 - .../vendor/react-native/Batchinator/index.js | 7 +- .../react-native/FillRateHelper/index.js | 2 + .../src/vendor/react-native/FlatList/index.js | 178 +++---- .../react-native/JSEventLoopWatchdog/index.js | 13 +- .../react-native/LayoutAnimation/index.js | 151 +++--- .../RCTDeviceEventEmitter.js | 14 + .../react-native/NativeEventEmitter/index.js | 7 +- .../vendor/react-native/PanResponder/index.js | 290 +++++++++--- .../src/vendor/react-native/SHA | 4 +- .../vendor/react-native/SectionList/index.js | 79 ++-- .../react-native/StaticContainer/index.js | 44 +- .../react-native/StaticRenderer/index.js | 50 +- .../react-native/SwipeableFlatList/index.js | 187 -------- .../vendor/react-native/SwipeableRow/index.js | 387 ---------------- .../react-native/TouchHistoryMath/index.js | 57 ++- .../react-native/Types/CoreEventTypes.js | 137 ++++++ .../react-native/ViewabilityHelper/index.js | 16 +- .../react-native/VirtualizeUtils/index.js | 17 +- .../react-native/VirtualizedList/index.js | 437 ++++++++++++------ .../VirtualizedSectionList/index.js | 285 +++++++----- .../vendor/react-native/deepDiffer/index.js | 72 +++ .../emitter/EmitterSubscription.js | 5 +- .../react-native/emitter/EventEmitter.js | 50 +- .../emitter/EventEmitterWithHolding.js | 17 +- .../react-native/emitter/EventHolder.js | 14 +- .../react-native/emitter/EventSubscription.js | 8 +- .../emitter/EventSubscriptionVendor.js | 15 +- .../react-native/emitter/EventValidator.js | 38 +- .../react-native/emitter/mixInEventEmitter.js | 16 +- .../src/vendor/react-native/isEmpty/index.js | 27 -- scripts/babel/preset.js | 3 +- yarn.lock | 8 + 36 files changed, 1305 insertions(+), 1346 deletions(-) delete mode 100644 packages/react-native-web/src/exports/SwipeableFlatList/index.js delete mode 100644 packages/react-native-web/src/vendor/react-native/SwipeableFlatList/index.js delete mode 100644 packages/react-native-web/src/vendor/react-native/SwipeableRow/index.js create mode 100644 packages/react-native-web/src/vendor/react-native/Types/CoreEventTypes.js create mode 100644 packages/react-native-web/src/vendor/react-native/deepDiffer/index.js delete mode 100644 packages/react-native-web/src/vendor/react-native/isEmpty/index.js diff --git a/package.json b/package.json index 2b2fce1a..eafce2e7 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@babel/cli": "^7.2.3", "@babel/core": "^7.2.2", "@babel/plugin-proposal-class-properties": "^7.2.3", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.4.4", "@babel/plugin-proposal-object-rest-spread": "^7.2.0", "@babel/preset-env": "^7.2.3", "@babel/preset-flow": "^7.0.0", diff --git a/packages/babel-plugin-react-native-web/src/moduleMap.js b/packages/babel-plugin-react-native-web/src/moduleMap.js index 63290c81..dd832e60 100644 --- a/packages/babel-plugin-react-native-web/src/moduleMap.js +++ b/packages/babel-plugin-react-native-web/src/moduleMap.js @@ -45,7 +45,6 @@ module.exports = { Share: true, StatusBar: true, StyleSheet: true, - SwipeableFlatList: true, Switch: true, Systrace: true, TVEventHandler: true, diff --git a/packages/react-native-web/src/exports/SwipeableFlatList/index.js b/packages/react-native-web/src/exports/SwipeableFlatList/index.js deleted file mode 100644 index e41031ab..00000000 --- a/packages/react-native-web/src/exports/SwipeableFlatList/index.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Copyright (c) Nicolas Gallagher. - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - */ - -import SwipeableFlatList from '../../vendor/react-native/SwipeableFlatList'; -export default SwipeableFlatList; diff --git a/packages/react-native-web/src/index.js b/packages/react-native-web/src/index.js index 4a5d90f2..9b8853f7 100644 --- a/packages/react-native-web/src/index.js +++ b/packages/react-native-web/src/index.js @@ -48,7 +48,6 @@ import SafeAreaView from './exports/SafeAreaView'; import ScrollView from './exports/ScrollView'; import SectionList from './exports/SectionList'; import StatusBar from './exports/StatusBar'; -import SwipeableFlatList from './exports/SwipeableFlatList'; import Switch from './exports/Switch'; import Text from './exports/Text'; import TextInput from './exports/TextInput'; @@ -132,7 +131,6 @@ export { ScrollView, SectionList, StatusBar, - SwipeableFlatList, Switch, Text, TextInput, diff --git a/packages/react-native-web/src/vendor/react-native/Batchinator/index.js b/packages/react-native-web/src/vendor/react-native/Batchinator/index.js index 914d9e9b..64f33b9a 100644 --- a/packages/react-native-web/src/vendor/react-native/Batchinator/index.js +++ b/packages/react-native-web/src/vendor/react-native/Batchinator/index.js @@ -1,12 +1,15 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow + * @format + * @flow strict-local */ +'use strict'; + import InteractionManager from '../../../exports/InteractionManager'; /** diff --git a/packages/react-native-web/src/vendor/react-native/FillRateHelper/index.js b/packages/react-native-web/src/vendor/react-native/FillRateHelper/index.js index fb6952be..f5901435 100644 --- a/packages/react-native-web/src/vendor/react-native/FillRateHelper/index.js +++ b/packages/react-native-web/src/vendor/react-native/FillRateHelper/index.js @@ -8,6 +8,8 @@ * @format */ +'use strict'; + import performanceNow from 'fbjs/lib/performanceNow'; import warning from 'fbjs/lib/warning'; diff --git a/packages/react-native-web/src/vendor/react-native/FlatList/index.js b/packages/react-native-web/src/vendor/react-native/FlatList/index.js index c9512a1f..726edb0f 100644 --- a/packages/react-native-web/src/vendor/react-native/FlatList/index.js +++ b/packages/react-native-web/src/vendor/react-native/FlatList/index.js @@ -1,23 +1,30 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @noflow + * @flow * @format */ +'use strict'; -import UnimplementedView from '../../../modules/UnimplementedView'; -import React from 'react'; +import deepDiffer from '../deepDiffer'; +import * as React from 'react'; +import StyleSheet from '../../../exports/StyleSheet'; import View from '../../../exports/View'; -import VirtualizedList, { type Props as VirtualizedListProps } from '../VirtualizedList'; +import VirtualizedList from '../VirtualizedList'; + +import invariant from 'fbjs/lib/invariant'; + +import type {ViewProps} from '../../../exports/View/ViewPropTypes'; + import type { ViewabilityConfig, ViewToken, - ViewabilityConfigCallbackPair + ViewabilityConfigCallbackPair, } from '../ViewabilityHelper'; -import invariant from 'fbjs/lib/invariant'; +import type {Props as VirtualizedListProps} from '../VirtualizedList'; export type SeparatorsObj = { highlight: () => void, @@ -25,6 +32,8 @@ export type SeparatorsObj = { updateProps: (select: 'leading' | 'trailing', newProps: Object) => void, }; +type ViewStyleProp = $PropertyType; + type RequiredProps = { /** * Takes an item from `data` and renders it into the list. Example usage: @@ -56,7 +65,7 @@ type RequiredProps = { item: ItemT, index: number, separators: SeparatorsObj, - }) => ?React.Element, + }) => ?React.Node, /** * For simplicity, data is just a plain array. If you want to use something else, like an * immutable list, use the underlying `VirtualizedList` directly. @@ -81,15 +90,23 @@ type OptionalProps = { * a rendered element. */ ListFooterComponent?: ?(React.ComponentType | React.Element), + /** + * Styling for internal View for ListFooterComponent + */ + ListFooterComponentStyle?: ViewStyleProp, /** * Rendered at the top of all the items. Can be a React Component Class, a render function, or * a rendered element. */ ListHeaderComponent?: ?(React.ComponentType | React.Element), + /** + * Styling for internal View for ListHeaderComponent + */ + ListHeaderComponentStyle?: ViewStyleProp, /** * Optional custom style for multi-item rows generated when numColumns > 1. */ - columnWrapperStyle?: any, + columnWrapperStyle?: ViewStyleProp, /** * A marker property for telling the list to re-render (since it implements `PureComponent`). If * any of your `renderItem`, Header, Footer, etc. functions depend on anything outside of the @@ -174,7 +191,10 @@ type OptionalProps = { * @platform android */ progressViewOffset?: number, - legacyImplementation?: ?boolean, + /** + * The legacy implementation is no longer supported. + */ + legacyImplementation?: empty, /** * Set this true while waiting for new data from a refresh. */ @@ -202,6 +222,12 @@ export type Props = RequiredProps & const defaultProps = { ...VirtualizedList.defaultProps, numColumns: 1, + /** + * Enabling this prop on Android greatly improves scrolling performance with no known issues. + * The alternative is that scrolling on Android is unusably bad. Enabling it on iOS has a few + * known issues. + */ + removeClippedSubviews: false, }; export type DefaultProps = typeof defaultProps; @@ -408,41 +434,15 @@ class FlatList extends React.PureComponent, void> { } } - setNativeProps(props: Object) { + setNativeProps(props: {[string]: mixed}) { if (this._listRef) { this._listRef.setNativeProps(props); } } - UNSAFE_componentWillMount() { - this._checkProps(this.props); - } - - UNSAFE_componentWillReceiveProps(nextProps: Props) { - invariant( - nextProps.numColumns === this.props.numColumns, - 'Changing numColumns on the fly is not supported. Change the key prop on FlatList when ' + - 'changing the number of columns to force a fresh render of the component.', - ); - invariant( - nextProps.onViewableItemsChanged === this.props.onViewableItemsChanged, - 'Changing onViewableItemsChanged on the fly is not supported', - ); - invariant( - nextProps.viewabilityConfig === this.props.viewabilityConfig, - 'Changing viewabilityConfig on the fly is not supported', - ); - invariant( - nextProps.viewabilityConfigCallbackPairs === - this.props.viewabilityConfigCallbackPairs, - 'Changing viewabilityConfigCallbackPairs on the fly is not supported', - ); - - this._checkProps(nextProps); - } - - constructor(props: Props<*>) { + constructor(props: Props) { super(props); + this._checkProps(this.props); if (this.props.viewabilityConfigCallbackPairs) { this._virtualizedListPairs = this.props.viewabilityConfigCallbackPairs.map( pair => ({ @@ -465,8 +465,30 @@ class FlatList extends React.PureComponent, void> { } } - _hasWarnedLegacy = false; - _listRef: null | VirtualizedList; + componentDidUpdate(prevProps: Props) { + invariant( + prevProps.numColumns === this.props.numColumns, + 'Changing numColumns on the fly is not supported. Change the key prop on FlatList when ' + + 'changing the number of columns to force a fresh render of the component.', + ); + invariant( + prevProps.onViewableItemsChanged === this.props.onViewableItemsChanged, + 'Changing onViewableItemsChanged on the fly is not supported', + ); + invariant( + !deepDiffer(prevProps.viewabilityConfig, this.props.viewabilityConfig), + 'Changing viewabilityConfig on the fly is not supported', + ); + invariant( + prevProps.viewabilityConfigCallbackPairs === + this.props.viewabilityConfigCallbackPairs, + 'Changing viewabilityConfigCallbackPairs on the fly is not supported', + ); + + this._checkProps(this.props); + } + + _listRef: ?React.ElementRef; _virtualizedListPairs: Array = []; _captureRef = ref => { @@ -478,7 +500,6 @@ class FlatList extends React.PureComponent, void> { getItem, getItemCount, horizontal, - legacyImplementation, numColumns, columnWrapperStyle, onViewableItemsChanged, @@ -496,21 +517,6 @@ class FlatList extends React.PureComponent, void> { 'columnWrapperStyle not supported for single column lists', ); } - if (legacyImplementation) { - invariant( - numColumns === 1, - 'Legacy list does not support multiple columns.', - ); - // Warning: may not have full feature parity and is meant more for debugging and performance - // comparison. - if (!this._hasWarnedLegacy) { - console.warn( - 'FlatList: Using legacyImplementation - some features not supported and performance ' + - 'may suffer', - ); - this._hasWarnedLegacy = true; - } - } invariant( !(onViewableItemsChanged && viewabilityConfigCallbackPairs), 'FlatList does not support setting both onViewableItemsChanged and ' + @@ -524,7 +530,9 @@ class FlatList extends React.PureComponent, void> { const ret = []; for (let kk = 0; kk < numColumns; kk++) { const item = data[index * numColumns + kk]; - item && ret.push(item); + if (item != null) { + ret.push(item); + } } return ret; } else { @@ -592,7 +600,7 @@ class FlatList extends React.PureComponent, void> { }; } - _renderItem = (info: Object) => { + _renderItem = (info: Object): ?React.Node => { const {renderItem, numColumns, columnWrapperStyle} = this.props; if (numColumns > 1) { const {item, index} = info; @@ -601,14 +609,20 @@ class FlatList extends React.PureComponent, void> { 'Expected array of items with numColumns > 1', ); return ( - + {item.map((it, kk) => { const element = renderItem({ item: it, index: index * numColumns + kk, separators: info.separators, }); - return element && React.cloneElement(element, {key: kk}); + return element != null ? ( + {element} + ) : null; })} ); @@ -618,34 +632,22 @@ class FlatList extends React.PureComponent, void> { }; render() { - if (this.props.legacyImplementation) { - return ( - /* $FlowFixMe(>=0.66.0 site=react_native_fb) This comment suppresses an - * error found when Flow v0.66 was deployed. To see the error delete - * this comment and run Flow. */ - =0.66.0 site=react_native_fb) This comment suppresses - * an error found when Flow v0.66 was deployed. To see the error - * delete this comment and run Flow. */ - items={this.props.data} - ref={this._captureRef} - /> - ); - } else { - return ( - - ); - } + return ( + + ); } } +const styles = StyleSheet.create({ + row: {flexDirection: 'row'}, +}); + export default FlatList; diff --git a/packages/react-native-web/src/vendor/react-native/JSEventLoopWatchdog/index.js b/packages/react-native-web/src/vendor/react-native/JSEventLoopWatchdog/index.js index 1fc16b43..5beea967 100644 --- a/packages/react-native-web/src/vendor/react-native/JSEventLoopWatchdog/index.js +++ b/packages/react-native-web/src/vendor/react-native/JSEventLoopWatchdog/index.js @@ -11,14 +11,11 @@ 'use strict'; import infoLog from '../infoLog'; -/* $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. */ import performanceNow from 'fbjs/lib/performanceNow'; type Handler = { onIterate?: () => void, - onStall: (params: { lastInterval: number, busyTime: number }) => ?string + onStall: (params: {lastInterval: number, busyTime: number}) => ?string, }; /** @@ -35,7 +32,7 @@ type Handler = { */ const JSEventLoopWatchdog = { getStats: function(): Object { - return { stallCount, totalStallTime, longestStall, acceptableBusyTime }; + return {stallCount, totalStallTime, longestStall, acceptableBusyTime}; }, reset: function() { infoLog('JSEventLoopWatchdog: reset'); @@ -47,7 +44,7 @@ const JSEventLoopWatchdog = { addHandler: function(handler: Handler) { handlers.push(handler); }, - install: function({ thresholdMS }: { thresholdMS: number }) { + install: function({thresholdMS}: {thresholdMS: number}) { acceptableBusyTime = thresholdMS; if (installed) { return; @@ -66,7 +63,7 @@ const JSEventLoopWatchdog = { `JSEventLoopWatchdog: JS thread busy for ${busyTime}ms. ` + `${totalStallTime}ms in ${stallCount} stalls so far. `; handlers.forEach(handler => { - msg += handler.onStall({ lastInterval, busyTime }) || ''; + msg += handler.onStall({lastInterval, busyTime}) || ''; }); infoLog(msg); } @@ -77,7 +74,7 @@ const JSEventLoopWatchdog = { setTimeout(iteration, thresholdMS / 5); } iteration(); - } + }, }; let acceptableBusyTime = 0; diff --git a/packages/react-native-web/src/vendor/react-native/LayoutAnimation/index.js b/packages/react-native-web/src/vendor/react-native/LayoutAnimation/index.js index 423c9725..cfdf5a32 100644 --- a/packages/react-native-web/src/vendor/react-native/LayoutAnimation/index.js +++ b/packages/react-native-web/src/vendor/react-native/LayoutAnimation/index.js @@ -1,120 +1,88 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @noflow + * @flow * @format */ -import PropTypes from 'prop-types'; +'use strict'; + +import Platform from '../../../exports/Platform'; import UIManager from '../../../exports/UIManager'; const __DEV__ = process.env.NODE_ENV !== 'production'; -const { checkPropTypes } = PropTypes; -const Types = { - spring: 'spring', - linear: 'linear', - easeInEaseOut: 'easeInEaseOut', - easeIn: 'easeIn', - easeOut: 'easeOut', - keyboard: 'keyboard' -}; +type Type = + | 'spring' + | 'linear' + | 'easeInEaseOut' + | 'easeIn' + | 'easeOut' + | 'keyboard'; -const Properties = { - opacity: 'opacity', - scaleX: 'scaleX', - scaleY: 'scaleY', - scaleXY: 'scaleXY' -}; +type Property = 'opacity' | 'scaleX' | 'scaleY' | 'scaleXY'; -const animType = PropTypes.shape({ - duration: PropTypes.number, - delay: PropTypes.number, - springDamping: PropTypes.number, - initialVelocity: PropTypes.number, - type: PropTypes.oneOf(Object.keys(Types)).isRequired, - property: PropTypes.oneOf( - // Only applies to create/delete - Object.keys(Properties), - ), -}); - -type Anim = { +type AnimationConfig = $ReadOnly<{| duration?: number, delay?: number, springDamping?: number, initialVelocity?: number, - type?: $Enum, - property?: $Enum, -}; + type?: Type, + property?: Property, +|}>; -const configType = PropTypes.shape({ - duration: PropTypes.number.isRequired, - create: animType, - update: animType, - delete: animType, -}); - -type Config = { +type LayoutAnimationConfig = $ReadOnly<{| duration: number, - create?: Anim, - update?: Anim, - delete?: Anim, -}; + create?: AnimationConfig, + update?: AnimationConfig, + delete?: AnimationConfig, +|}>; -function checkConfig(config: Config, location: string, name: string) { - checkPropTypes({config: configType}, {config}, location, name); -} - -function configureNext(config: Config, onAnimationDidEnd?: Function) { - if (__DEV__) { - checkConfig(config, 'config', 'LayoutAnimation.configureNext'); +function configureNext( + config: LayoutAnimationConfig, + onAnimationDidEnd?: Function, +) { + if (!Platform.isTesting) { + UIManager.configureNextLayoutAnimation( + config, + onAnimationDidEnd ?? function() {}, + function() {} /* unused onError */, + ); } - UIManager.configureNextLayoutAnimation( - config, - onAnimationDidEnd || function() {}, - function() { - /* unused */ - }, - ); } -function create(duration: number, type, creationProp): Config { +function create( + duration: number, + type: Type, + property: Property, +): LayoutAnimationConfig { return { duration, - create: { - type, - property: creationProp, - }, - update: { - type, - }, - delete: { - type, - property: creationProp, - }, + create: {type, property}, + update: {type}, + delete: {type, property}, }; } const Presets = { - easeInEaseOut: create(300, Types.easeInEaseOut, Properties.opacity), - linear: create(500, Types.linear, Properties.opacity), + easeInEaseOut: create(300, 'easeInEaseOut', 'opacity'), + linear: create(500, 'linear', 'opacity'), spring: { duration: 700, create: { - type: Types.linear, - property: Properties.opacity, + type: 'linear', + property: 'opacity', }, update: { - type: Types.spring, + type: 'spring', springDamping: 0.4, }, delete: { - type: Types.linear, - property: Properties.opacity, + type: 'linear', + property: 'opacity', }, }, }; @@ -136,9 +104,8 @@ const LayoutAnimation = { * @param config Specifies animation properties: * * - `duration` in milliseconds - * - `create`, config for animating in new views (see `Anim` type) - * - `update`, config for animating views that have been updated - * (see `Anim` type) + * - `create`, `AnimationConfig` for animating in new views + * - `update`, `AnimationConfig` for animating views that have been updated * * @param onAnimationDidEnd Called when the animation finished. * Only supported on iOS. @@ -149,9 +116,23 @@ const LayoutAnimation = { * Helper for creating a config for `configureNext`. */ create, - Types, - Properties, - checkConfig, + Types: Object.freeze({ + spring: 'spring', + linear: 'linear', + easeInEaseOut: 'easeInEaseOut', + easeIn: 'easeIn', + easeOut: 'easeOut', + keyboard: 'keyboard', + }), + Properties: Object.freeze({ + opacity: 'opacity', + scaleX: 'scaleX', + scaleY: 'scaleY', + scaleXY: 'scaleXY', + }), + checkConfig(...args: Array) { + console.error('LayoutAnimation.checkConfig(...) has been disabled.'); + }, Presets, easeInEaseOut: configureNext.bind(null, Presets.easeInEaseOut), linear: configureNext.bind(null, Presets.linear), diff --git a/packages/react-native-web/src/vendor/react-native/NativeEventEmitter/RCTDeviceEventEmitter.js b/packages/react-native-web/src/vendor/react-native/NativeEventEmitter/RCTDeviceEventEmitter.js index a023eed1..8e3d0156 100644 --- a/packages/react-native-web/src/vendor/react-native/NativeEventEmitter/RCTDeviceEventEmitter.js +++ b/packages/react-native-web/src/vendor/react-native/NativeEventEmitter/RCTDeviceEventEmitter.js @@ -19,6 +19,20 @@ const __DEV__ = process.env.NODE_ENV !== 'production'; function checkNativeEventModule(eventType: ?string) { if (eventType) { + if (eventType.lastIndexOf('statusBar', 0) === 0) { + throw new Error( + '`' + + eventType + + '` event should be registered via the StatusBarIOS module', + ); + } + if (eventType.lastIndexOf('keyboard', 0) === 0) { + throw new Error( + '`' + + eventType + + '` event should be registered via the Keyboard module', + ); + } if (eventType === 'appStateDidChange' || eventType === 'memoryWarning') { throw new Error( '`' + diff --git a/packages/react-native-web/src/vendor/react-native/NativeEventEmitter/index.js b/packages/react-native-web/src/vendor/react-native/NativeEventEmitter/index.js index d361e8f2..0a5e89ad 100644 --- a/packages/react-native-web/src/vendor/react-native/NativeEventEmitter/index.js +++ b/packages/react-native-web/src/vendor/react-native/NativeEventEmitter/index.js @@ -1,18 +1,19 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule NativeEventEmitter + * @format * @flow */ + 'use strict'; -import invariant from 'fbjs/lib/invariant'; import EventEmitter from '../emitter/EventEmitter'; import RCTDeviceEventEmitter from './RCTDeviceEventEmitter'; +import invariant from 'fbjs/lib/invariant'; import type EmitterSubscription from '../emitter/EmitterSubscription'; diff --git a/packages/react-native-web/src/vendor/react-native/PanResponder/index.js b/packages/react-native-web/src/vendor/react-native/PanResponder/index.js index 0cbe01b6..faf59964 100644 --- a/packages/react-native-web/src/vendor/react-native/PanResponder/index.js +++ b/packages/react-native-web/src/vendor/react-native/PanResponder/index.js @@ -1,17 +1,28 @@ /** - * Copyright (c) 2013-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * 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'; + import InteractionManager from '../../../exports/InteractionManager'; import TouchHistoryMath from '../TouchHistoryMath'; -const currentCentroidXOfTouchesChangedAfter = TouchHistoryMath.currentCentroidXOfTouchesChangedAfter; -const currentCentroidYOfTouchesChangedAfter = TouchHistoryMath.currentCentroidYOfTouchesChangedAfter; -const previousCentroidXOfTouchesChangedAfter = TouchHistoryMath.previousCentroidXOfTouchesChangedAfter; -const previousCentroidYOfTouchesChangedAfter = TouchHistoryMath.previousCentroidYOfTouchesChangedAfter; +import type {PressEvent} from '../Types/CoreEventTypes'; + +const currentCentroidXOfTouchesChangedAfter = + TouchHistoryMath.currentCentroidXOfTouchesChangedAfter; +const currentCentroidYOfTouchesChangedAfter = + TouchHistoryMath.currentCentroidYOfTouchesChangedAfter; +const previousCentroidXOfTouchesChangedAfter = + TouchHistoryMath.previousCentroidXOfTouchesChangedAfter; +const previousCentroidYOfTouchesChangedAfter = + TouchHistoryMath.previousCentroidYOfTouchesChangedAfter; const currentCentroidX = TouchHistoryMath.currentCentroidX; const currentCentroidY = TouchHistoryMath.currentCentroidY; @@ -113,8 +124,94 @@ const currentCentroidY = TouchHistoryMath.currentCentroidY; * [PanResponder example in RNTester](https://github.com/facebook/react-native/blob/master/RNTester/js/PanResponderExample.js) */ -const PanResponder = { +export type GestureState = {| + /** + * ID of the gestureState - persisted as long as there at least one touch on screen + */ + stateID: number, + /** + * The latest screen coordinates of the recently-moved touch + */ + moveX: number, + + /** + * The latest screen coordinates of the recently-moved touch + */ + moveY: number, + + /** + * The screen coordinates of the responder grant + */ + x0: number, + + /** + * The screen coordinates of the responder grant + */ + y0: number, + + /** + * Accumulated distance of the gesture since the touch started + */ + dx: number, + + /** + * Accumulated distance of the gesture since the touch started + */ + dy: number, + + /** + * Current velocity of the gesture + */ + vx: number, + + /** + * Current velocity of the gesture + */ + vy: number, + + /** + * Number of touches currently on screen + */ + numberActiveTouches: number, + + /** + * All `gestureState` accounts for timeStamps up until this value + * + * @private + */ + _accountsForMovesUpTo: number, +|}; + +type ActiveCallback = ( + event: PressEvent, + gestureState: GestureState, +) => boolean; + +type PassiveCallback = (event: PressEvent, gestureState: GestureState) => mixed; + +type PanResponderConfig = $ReadOnly<{| + onMoveShouldSetPanResponder?: ?ActiveCallback, + onMoveShouldSetPanResponderCapture?: ?ActiveCallback, + onStartShouldSetPanResponder?: ?ActiveCallback, + onStartShouldSetPanResponderCapture?: ?ActiveCallback, + /** + * The body of `onResponderGrant` returns a bool, but the vast majority of + * callsites return void and this TODO notice is found in it: + * TODO: t7467124 investigate if this can be removed + */ + onPanResponderGrant?: ?(PassiveCallback | ActiveCallback), + onPanResponderReject?: ?PassiveCallback, + onPanResponderStart?: ?PassiveCallback, + onPanResponderEnd?: ?PassiveCallback, + onPanResponderRelease?: ?PassiveCallback, + onPanResponderMove?: ?PassiveCallback, + onPanResponderTerminate?: ?PassiveCallback, + onPanResponderTerminationRequest?: ?ActiveCallback, + onShouldBlockNativeResponder?: ?ActiveCallback, +|}>; + +const PanResponder = { /** * * A graphical explanation of the touch data flow: @@ -178,7 +275,7 @@ const PanResponder = { * - vx/vy: Velocity. */ - _initializeGestureState: function (gestureState) { + _initializeGestureState(gestureState: GestureState) { gestureState.moveX = 0; gestureState.moveY = 0; gestureState.x0 = 0; @@ -216,20 +313,36 @@ const PanResponder = { * typical responder callback pattern (without using `PanResponder`), but * avoids more dispatches than necessary. */ - _updateGestureStateOnMove: function (gestureState, touchHistory) { + _updateGestureStateOnMove( + gestureState: GestureState, + touchHistory: $PropertyType, + ) { gestureState.numberActiveTouches = touchHistory.numberActiveTouches; - gestureState.moveX = currentCentroidXOfTouchesChangedAfter(touchHistory, gestureState._accountsForMovesUpTo); - gestureState.moveY = currentCentroidYOfTouchesChangedAfter(touchHistory, gestureState._accountsForMovesUpTo); + gestureState.moveX = currentCentroidXOfTouchesChangedAfter( + touchHistory, + gestureState._accountsForMovesUpTo, + ); + gestureState.moveY = currentCentroidYOfTouchesChangedAfter( + touchHistory, + gestureState._accountsForMovesUpTo, + ); const movedAfter = gestureState._accountsForMovesUpTo; - const prevX = previousCentroidXOfTouchesChangedAfter(touchHistory, movedAfter); + const prevX = previousCentroidXOfTouchesChangedAfter( + touchHistory, + movedAfter, + ); const x = currentCentroidXOfTouchesChangedAfter(touchHistory, movedAfter); - const prevY = previousCentroidYOfTouchesChangedAfter(touchHistory, movedAfter); + const prevY = previousCentroidYOfTouchesChangedAfter( + touchHistory, + movedAfter, + ); const y = currentCentroidYOfTouchesChangedAfter(touchHistory, movedAfter); const nextDX = gestureState.dx + (x - prevX); const nextDY = gestureState.dy + (y - prevY); // TODO: This must be filtered intelligently. - const dt = touchHistory.mostRecentTimeStamp - gestureState._accountsForMovesUpTo; + const dt = + touchHistory.mostRecentTimeStamp - gestureState._accountsForMovesUpTo; gestureState.vx = (nextDX - gestureState.dx) / dt; gestureState.vy = (nextDY - gestureState.dy) / dt; @@ -270,117 +383,153 @@ const PanResponder = { * accordingly. (numberActiveTouches) may not be totally accurate unless you * are the responder. */ - create: function (config) { + create(config: PanResponderConfig) { const interactionState = { handle: (null: ?number), }; - const gestureState = { + const gestureState: GestureState = { // Useful for debugging stateID: Math.random(), + moveX: 0, + moveY: 0, + x0: 0, + y0: 0, + dx: 0, + dy: 0, + vx: 0, + vy: 0, + numberActiveTouches: 0, + _accountsForMovesUpTo: 0, }; - PanResponder._initializeGestureState(gestureState); const panHandlers = { - onStartShouldSetResponder: function (e) { - return config.onStartShouldSetPanResponder === undefined ? - false : - config.onStartShouldSetPanResponder(e, gestureState); + onStartShouldSetResponder(event: PressEvent): boolean { + return config.onStartShouldSetPanResponder == null + ? false + : config.onStartShouldSetPanResponder(event, gestureState); }, - onMoveShouldSetResponder: function (e) { - return config.onMoveShouldSetPanResponder === undefined ? - false : - config.onMoveShouldSetPanResponder(e, gestureState); + onMoveShouldSetResponder(event: PressEvent): boolean { + return config.onMoveShouldSetPanResponder == null + ? false + : config.onMoveShouldSetPanResponder(event, gestureState); }, - onStartShouldSetResponderCapture: function (e) { + onStartShouldSetResponderCapture(event: PressEvent): boolean { // TODO: Actually, we should reinitialize the state any time // touches.length increases from 0 active to > 0 active. - if (e.nativeEvent.touches.length === 1) { + if (event.nativeEvent.touches.length === 1) { PanResponder._initializeGestureState(gestureState); } - gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches; - return config.onStartShouldSetPanResponderCapture !== undefined ? - config.onStartShouldSetPanResponderCapture(e, gestureState) : - false; + gestureState.numberActiveTouches = + event.touchHistory.numberActiveTouches; + return config.onStartShouldSetPanResponderCapture != null + ? config.onStartShouldSetPanResponderCapture(event, gestureState) + : false; }, - onMoveShouldSetResponderCapture: function (e) { - const touchHistory = e.touchHistory; + onMoveShouldSetResponderCapture(event: PressEvent): boolean { + const touchHistory = event.touchHistory; // Responder system incorrectly dispatches should* to current responder // Filter out any touch moves past the first one - we would have // already processed multi-touch geometry during the first event. - if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) { + if ( + gestureState._accountsForMovesUpTo === + touchHistory.mostRecentTimeStamp + ) { return false; } PanResponder._updateGestureStateOnMove(gestureState, touchHistory); - return config.onMoveShouldSetPanResponderCapture ? - config.onMoveShouldSetPanResponderCapture(e, gestureState) : - false; + return config.onMoveShouldSetPanResponderCapture + ? config.onMoveShouldSetPanResponderCapture(event, gestureState) + : false; }, - onResponderGrant: function (e) { + onResponderGrant(event: PressEvent): boolean { if (!interactionState.handle) { interactionState.handle = InteractionManager.createInteractionHandle(); } - gestureState.x0 = currentCentroidX(e.touchHistory); - gestureState.y0 = currentCentroidY(e.touchHistory); + gestureState.x0 = currentCentroidX(event.touchHistory); + gestureState.y0 = currentCentroidY(event.touchHistory); gestureState.dx = 0; gestureState.dy = 0; if (config.onPanResponderGrant) { - config.onPanResponderGrant(e, gestureState); + config.onPanResponderGrant(event, gestureState); } // TODO: t7467124 investigate if this can be removed - return config.onShouldBlockNativeResponder === undefined ? - true : - config.onShouldBlockNativeResponder(); + return config.onShouldBlockNativeResponder == null + ? true + : config.onShouldBlockNativeResponder(event, gestureState); }, - onResponderReject: function (e) { - clearInteractionHandle(interactionState, config.onPanResponderReject, e, gestureState); + onResponderReject(event: PressEvent): void { + clearInteractionHandle( + interactionState, + config.onPanResponderReject, + event, + gestureState, + ); }, - onResponderRelease: function (e) { - clearInteractionHandle(interactionState, config.onPanResponderRelease, e, gestureState); + onResponderRelease(event: PressEvent): void { + clearInteractionHandle( + interactionState, + config.onPanResponderRelease, + event, + gestureState, + ); PanResponder._initializeGestureState(gestureState); }, - onResponderStart: function (e) { - const touchHistory = e.touchHistory; + onResponderStart(event: PressEvent): void { + const touchHistory = event.touchHistory; gestureState.numberActiveTouches = touchHistory.numberActiveTouches; if (config.onPanResponderStart) { - config.onPanResponderStart(e, gestureState); + config.onPanResponderStart(event, gestureState); } }, - onResponderMove: function (e) { - const touchHistory = e.touchHistory; + onResponderMove(event: PressEvent): void { + const touchHistory = event.touchHistory; // Guard against the dispatch of two touch moves when there are two // simultaneously changed touches. - if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) { + if ( + gestureState._accountsForMovesUpTo === + touchHistory.mostRecentTimeStamp + ) { return; } // Filter out any touch moves past the first one - we would have // already processed multi-touch geometry during the first event. PanResponder._updateGestureStateOnMove(gestureState, touchHistory); if (config.onPanResponderMove) { - config.onPanResponderMove(e, gestureState); + config.onPanResponderMove(event, gestureState); } }, - onResponderEnd: function (e) { - const touchHistory = e.touchHistory; + onResponderEnd(event: PressEvent): void { + const touchHistory = event.touchHistory; gestureState.numberActiveTouches = touchHistory.numberActiveTouches; - clearInteractionHandle(interactionState, config.onPanResponderEnd, e, gestureState); + clearInteractionHandle( + interactionState, + config.onPanResponderEnd, + event, + gestureState, + ); }, - onResponderTerminate: function (e) { - clearInteractionHandle(interactionState, config.onPanResponderTerminate, e, gestureState); + onResponderTerminate(event: PressEvent): void { + clearInteractionHandle( + interactionState, + config.onPanResponderTerminate, + event, + gestureState, + ); PanResponder._initializeGestureState(gestureState); }, - onResponderTerminationRequest: function (e) { - return config.onPanResponderTerminationRequest === undefined ? - true : - config.onPanResponderTerminationRequest(e, gestureState); - } + onResponderTerminationRequest(event: PressEvent): boolean { + return config.onPanResponderTerminationRequest == null + ? true + : config.onPanResponderTerminationRequest(event, gestureState); + }, }; return { panHandlers, @@ -388,14 +537,14 @@ const PanResponder = { return interactionState.handle; }, }; - } + }, }; function clearInteractionHandle( interactionState: {handle: ?number}, - callback: Function, - event: Object, - gestureState: Object + callback: ?(ActiveCallback | PassiveCallback), + event: PressEvent, + gestureState: GestureState, ) { if (interactionState.handle) { InteractionManager.clearInteractionHandle(interactionState.handle); @@ -406,4 +555,9 @@ function clearInteractionHandle( } } +export type PanResponderInstance = $Call< + $PropertyType, + PanResponderConfig, +>; + export default PanResponder; diff --git a/packages/react-native-web/src/vendor/react-native/SHA b/packages/react-native-web/src/vendor/react-native/SHA index e265881d..80553eed 100644 --- a/packages/react-native-web/src/vendor/react-native/SHA +++ b/packages/react-native-web/src/vendor/react-native/SHA @@ -1,2 +1,2 @@ -facebook/react-native@0.55.4 -facebook/react-native@370bcffba748e895ad8afa825bfef40bff859c95 +facebook/react-native@0.60.0-rc.3 +facebook/react-native@8a43321271bd58bbe0bca27593e121077d7a4065 diff --git a/packages/react-native-web/src/vendor/react-native/SectionList/index.js b/packages/react-native-web/src/vendor/react-native/SectionList/index.js index 9e7ff75c..39ebafb8 100644 --- a/packages/react-native-web/src/vendor/react-native/SectionList/index.js +++ b/packages/react-native-web/src/vendor/react-native/SectionList/index.js @@ -1,51 +1,28 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @noflow + * @flow * @format */ -import UnimplementedView from '../../../modules/UnimplementedView'; +'use strict'; + import Platform from '../../../exports/Platform'; -import React from 'react'; +import * as React from 'react'; import ScrollView from '../../../exports/ScrollView'; -import VirtualizedSectionList, { - type Props as VirtualizedSectionListProps +import VirtualizedSectionList from '../VirtualizedSectionList'; + +import type {ViewToken} from '../ViewabilityHelper'; +import type { + SectionBase as _SectionBase, + Props as VirtualizedSectionListProps, } from '../VirtualizedSectionList'; -import type { ViewToken } from '../ViewabilityHelper'; type Item = any; -export type SectionBase = { - /** - * The data for rendering items in this section. - */ - data: $ReadOnlyArray, - /** - * Optional key to keep track of section re-ordering. If you don't plan on re-ordering sections, - * the array index will be used by default. - */ - key?: string, - - // Optional props will override list-wide props just for this section. - renderItem?: ?(info: { - item: SectionItemT, - index: number, - section: SectionBase, - separators: { - highlight: () => void, - unhighlight: () => void, - updateProps: (select: 'leading' | 'trailing', newProps: Object) => void, - }, - }) => ?React.Element, - ItemSeparatorComponent?: ?React.ComponentType, - keyExtractor?: (item: SectionItemT) => string, - - // TODO: support more optional/override props - // onViewableItemsChanged?: ... -}; +export type SectionBase = _SectionBase; type RequiredProps> = { /** @@ -179,7 +156,10 @@ type OptionalProps> = { */ stickySectionHeadersEnabled?: boolean, - legacyImplementation?: ?boolean, + /** + * The legacy implementation is no longer supported. + */ + legacyImplementation?: empty, }; export type Props = RequiredProps & @@ -272,7 +252,9 @@ class SectionList> extends React.PureComponent< viewOffset?: number, viewPosition?: number, }) { - this._wrapperListRef.scrollToLocation(params); + if (this._wrapperListRef != null) { + this._wrapperListRef.scrollToLocation(params); + } } /** @@ -320,20 +302,21 @@ class SectionList> extends React.PureComponent< } render() { - const List = this.props.legacyImplementation - ? UnimplementedView - : VirtualizedSectionList; - /* $FlowFixMe(>=0.66.0 site=react_native_fb) This comment suppresses an - * error found when Flow v0.66 was deployed. To see the error delete this - * comment and run Flow. */ - return ; + return ( + /* $FlowFixMe(>=0.66.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.66 was deployed. To see the error delete this + * comment and run Flow. */ + items.length} + getItem={(items, index) => items[index]} + /> + ); } - _wrapperListRef: VirtualizedSectionList; + _wrapperListRef: ?React.ElementRef; _captureRef = ref => { - /* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This comment - * suppresses an error when upgrading Flow's support for React. To see the - * error delete this comment and run Flow. */ this._wrapperListRef = ref; }; } diff --git a/packages/react-native-web/src/vendor/react-native/StaticContainer/index.js b/packages/react-native-web/src/vendor/react-native/StaticContainer/index.js index b64bf443..1dc0e59f 100644 --- a/packages/react-native-web/src/vendor/react-native/StaticContainer/index.js +++ b/packages/react-native-web/src/vendor/react-native/StaticContainer/index.js @@ -1,15 +1,16 @@ /** - * Copyright (c) 2015-present, Nicolas Gallagher. - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow + * @format + * @flow strict-local */ -import { any, bool } from 'prop-types'; -import { Children, Component } from 'react'; +'use strict'; + +import * as React from 'react'; /** * Renders static content efficiently by allowing React to short-circuit the @@ -27,23 +28,28 @@ import { Children, Component } from 'react'; * React reconciliation. */ -type Props = { - children: any, - shouldUpdate: boolean -}; - -export default class StaticContainer extends Component { - static propTypes = { - children: any.isRequired, - shouldUpdate: bool.isRequired - }; - - shouldComponentUpdate(nextProps: { shouldUpdate: boolean }): boolean { - return nextProps.shouldUpdate; +type Props = $ReadOnly<{| + /** + * Whether or not this component should update. + */ + shouldUpdate: ?boolean, + /** + * Content short-circuited by React reconciliation process. + */ + children: React.Node, +|}>; +class StaticContainer extends React.Component { + shouldComponentUpdate(nextProps: Props): boolean { + return !!nextProps.shouldUpdate; } render() { const child = this.props.children; - return child === null || child === false ? null : Children.only(child); + return child === null || child === false + ? null + : React.Children.only(child); } } + +export default StaticContainer; + diff --git a/packages/react-native-web/src/vendor/react-native/StaticRenderer/index.js b/packages/react-native-web/src/vendor/react-native/StaticRenderer/index.js index 0cfe9348..2a5ee673 100644 --- a/packages/react-native-web/src/vendor/react-native/StaticRenderer/index.js +++ b/packages/react-native-web/src/vendor/react-native/StaticRenderer/index.js @@ -1,46 +1,38 @@ /** - * Copyright (c) 2015-present, Nicolas Gallagher. - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * + * @format * @flow */ -import { Component } from 'react'; -import { bool, func } from 'prop-types'; +'use strict'; -/** - * Renders static content efficiently by allowing React to short-circuit the - * reconciliation process. This component should be used when you know that a - * subtree of components will never need to be updated. - * - * const someValue = ...; // We know for certain this value will never change. - * return ( - * } /> - * ); - * - * Typically, you will not need to use this component and should opt for normal - * React reconciliation. - */ +import * as React from 'react'; -type Props = { - render: Function, - shouldUpdate: boolean -}; +type Props = $ReadOnly<{| + /** + * Indicates whether the render function needs to be called again + */ + shouldUpdate: boolean, + /** + * () => renderable + * A function that returns a renderable component + */ + render: () => React.Node, +|}>; -export default class StaticRenderer extends Component { - static propTypes = { - render: func.isRequired, - shouldUpdate: bool.isRequired - }; - - shouldComponentUpdate(nextProps: { shouldUpdate: boolean }): boolean { +class StaticRenderer extends React.Component { + shouldComponentUpdate(nextProps: Props): boolean { return nextProps.shouldUpdate; } - render() { + render(): React.Node { return this.props.render(); } } + +export default StaticRenderer; + diff --git a/packages/react-native-web/src/vendor/react-native/SwipeableFlatList/index.js b/packages/react-native-web/src/vendor/react-native/SwipeableFlatList/index.js deleted file mode 100644 index 929983c6..00000000 --- a/packages/react-native-web/src/vendor/react-native/SwipeableFlatList/index.js +++ /dev/null @@ -1,187 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @providesModule SwipeableFlatList - * @noflow - * @format - */ -'use strict'; - -import type {Props as FlatListProps} from '../FlatList'; -import type {renderItemType} from '../VirtualizedList'; - -import PropTypes from 'prop-types'; -import React from 'react'; -import SwipeableRow from '../SwipeableRow'; -import FlatList from '../FlatList'; - -type SwipableListProps = { - /** - * To alert the user that swiping is possible, the first row can bounce - * on component mount. - */ - bounceFirstRowOnMount: boolean, - // Maximum distance to open to after a swipe - maxSwipeDistance: number | (Object => number), - // Callback method to render the view that will be unveiled on swipe - renderQuickActions: renderItemType, -}; - -type Props = SwipableListProps & FlatListProps; - -type State = { - openRowKey: ?string, -}; - -/** - * A container component that renders multiple SwipeableRow's in a FlatList - * implementation. This is designed to be a drop-in replacement for the - * standard React Native `FlatList`, so use it as if it were a FlatList, but - * with extra props, i.e. - * - * - * - * SwipeableRow can be used independently of this component, but the main - * benefit of using this component is - * - * - It ensures that at most 1 row is swiped open (auto closes others) - * - It can bounce the 1st row of the list so users know it's swipeable - * - Increase performance on iOS by locking list swiping when row swiping is occurring - * - More to come - */ - -class SwipeableFlatList extends React.Component, State> { - props: Props; - state: State; - - _flatListRef: ?FlatList = null; - _shouldBounceFirstRowOnMount: boolean = false; - - static propTypes = { - ...FlatList.propTypes, - - /** - * To alert the user that swiping is possible, the first row can bounce - * on component mount. - */ - bounceFirstRowOnMount: PropTypes.bool.isRequired, - - // Maximum distance to open to after a swipe - maxSwipeDistance: PropTypes.oneOfType([PropTypes.number, PropTypes.func]) - .isRequired, - - // Callback method to render the view that will be unveiled on swipe - renderQuickActions: PropTypes.func.isRequired, - }; - - static defaultProps = { - ...FlatList.defaultProps, - bounceFirstRowOnMount: true, - renderQuickActions: () => null, - }; - - constructor(props: Props, context: any): void { - super(props, context); - this.state = { - openRowKey: null, - }; - - this._shouldBounceFirstRowOnMount = this.props.bounceFirstRowOnMount; - } - - render(): React.Node { - return ( - { - this._flatListRef = ref; - }} - onScroll={this._onScroll} - renderItem={this._renderItem} - /> - ); - } - - _onScroll = (e): void => { - // Close any opens rows on ListView scroll - if (this.state.openRowKey) { - this.setState({ - openRowKey: null, - }); - } - - this.props.onScroll && this.props.onScroll(e); - }; - - _renderItem = (info: Object): ?React.Element => { - const slideoutView = this.props.renderQuickActions(info); - const key = this.props.keyExtractor(info.item, info.index); - - // If renderQuickActions is unspecified or returns falsey, don't allow swipe - if (!slideoutView) { - return this.props.renderItem(info); - } - - let shouldBounceOnMount = false; - if (this._shouldBounceFirstRowOnMount) { - this._shouldBounceFirstRowOnMount = false; - shouldBounceOnMount = true; - } - - return ( - this._onOpen(key)} - onClose={() => this._onClose(key)} - shouldBounceOnMount={shouldBounceOnMount} - onSwipeEnd={this._setListViewScrollable} - onSwipeStart={this._setListViewNotScrollable}> - {this.props.renderItem(info)} - - ); - }; - - // This enables rows having variable width slideoutView. - _getMaxSwipeDistance(info: Object): number { - if (typeof this.props.maxSwipeDistance === 'function') { - return this.props.maxSwipeDistance(info); - } - - return this.props.maxSwipeDistance; - } - - _setListViewScrollableTo(value: boolean) { - if (this._flatListRef) { - this._flatListRef.setNativeProps({ - scrollEnabled: value, - }); - } - } - - _setListViewScrollable = () => { - this._setListViewScrollableTo(true); - }; - - _setListViewNotScrollable = () => { - this._setListViewScrollableTo(false); - }; - - _onOpen(key: any): void { - this.setState({ - openRowKey: key, - }); - } - - _onClose(key: any): void { - this.setState({ - openRowKey: null, - }); - } -} - -export default SwipeableFlatList; diff --git a/packages/react-native-web/src/vendor/react-native/SwipeableRow/index.js b/packages/react-native-web/src/vendor/react-native/SwipeableRow/index.js deleted file mode 100644 index 7be98632..00000000 --- a/packages/react-native-web/src/vendor/react-native/SwipeableRow/index.js +++ /dev/null @@ -1,387 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @providesModule SwipeableRow - * @noflow - */ -'use strict'; - -import Animated from '../../../exports/Animated'; -import I18nManager from '../../../exports/I18nManager'; -import PanResponder from '../../../exports/PanResponder'; -import React from 'react'; -import PropTypes from 'prop-types'; -import StyleSheet from '../../../exports/StyleSheet'; -/* $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. */ -import TimerMixin from 'react-timer-mixin'; -import View from '../../../exports/View'; - -import createReactClass from 'create-react-class'; -import emptyFunction from 'fbjs/lib/emptyFunction'; - -const isRTL = () => I18nManager.isRTL; - -// NOTE: Eventually convert these consts to an input object of configurations - -// Position of the left of the swipable item when closed -const CLOSED_LEFT_POSITION = 0; -// Minimum swipe distance before we recognize it as such -const HORIZONTAL_SWIPE_DISTANCE_THRESHOLD = 10; -// Minimum swipe speed before we fully animate the user's action (open/close) -const HORIZONTAL_FULL_SWIPE_SPEED_THRESHOLD = 0.3; -// Factor to divide by to get slow speed; i.e. 4 means 1/4 of full speed -const SLOW_SPEED_SWIPE_FACTOR = 4; -// Time, in milliseconds, of how long the animated swipe should be -const SWIPE_DURATION = 300; - -/** - * On SwipeableListView mount, the 1st item will bounce to show users it's - * possible to swipe - */ -const ON_MOUNT_BOUNCE_DELAY = 700; -const ON_MOUNT_BOUNCE_DURATION = 400; - -// Distance left of closed position to bounce back when right-swiping from closed -const RIGHT_SWIPE_BOUNCE_BACK_DISTANCE = 30; -const RIGHT_SWIPE_BOUNCE_BACK_DURATION = 300; -/** - * Max distance of right swipe to allow (right swipes do functionally nothing). - * Must be multiplied by SLOW_SPEED_SWIPE_FACTOR because gestureState.dx tracks - * how far the finger swipes, and not the actual animation distance. -*/ -const RIGHT_SWIPE_THRESHOLD = 30 * SLOW_SPEED_SWIPE_FACTOR; - -/** - * Creates a swipable row that allows taps on the main item and a custom View - * on the item hidden behind the row. Typically this should be used in - * conjunction with SwipeableListView for additional functionality, but can be - * used in a normal ListView. See the renderRow for SwipeableListView to see how - * to use this component separately. - */ -const SwipeableRow = createReactClass({ - displayName: 'SwipeableRow', - _panResponder: {}, - _previousLeft: CLOSED_LEFT_POSITION, - - mixins: [TimerMixin], - - propTypes: { - children: PropTypes.any, - isOpen: PropTypes.bool, - preventSwipeRight: PropTypes.bool, - maxSwipeDistance: PropTypes.number.isRequired, - onOpen: PropTypes.func.isRequired, - onClose: PropTypes.func.isRequired, - onSwipeEnd: PropTypes.func.isRequired, - onSwipeStart: PropTypes.func.isRequired, - // Should bounce the row on mount - shouldBounceOnMount: PropTypes.bool, - /** - * A ReactElement that is unveiled when the user swipes - */ - slideoutView: PropTypes.node.isRequired, - /** - * The minimum swipe distance required before fully animating the swipe. If - * the user swipes less than this distance, the item will return to its - * previous (open/close) position. - */ - swipeThreshold: PropTypes.number.isRequired, - }, - - getInitialState(): Object { - return { - currentLeft: new Animated.Value(this._previousLeft), - /** - * In order to render component A beneath component B, A must be rendered - * before B. However, this will cause "flickering", aka we see A briefly - * then B. To counter this, _isSwipeableViewRendered flag is used to set - * component A to be transparent until component B is loaded. - */ - isSwipeableViewRendered: false, - rowHeight: (null: ?number), - }; - }, - - getDefaultProps(): Object { - return { - isOpen: false, - preventSwipeRight: false, - maxSwipeDistance: 0, - onOpen: emptyFunction, - onClose: emptyFunction, - onSwipeEnd: emptyFunction, - onSwipeStart: emptyFunction, - swipeThreshold: 30, - }; - }, - - UNSAFE_componentWillMount(): void { - this._panResponder = PanResponder.create({ - onMoveShouldSetPanResponderCapture: this._handleMoveShouldSetPanResponderCapture, - onPanResponderGrant: this._handlePanResponderGrant, - onPanResponderMove: this._handlePanResponderMove, - onPanResponderRelease: this._handlePanResponderEnd, - onPanResponderTerminationRequest: this._onPanResponderTerminationRequest, - onPanResponderTerminate: this._handlePanResponderEnd, - onShouldBlockNativeResponder: (event, gestureState) => false, - }); - }, - - componentDidMount(): void { - if (this.props.shouldBounceOnMount) { - /** - * Do the on mount bounce after a delay because if we animate when other - * components are loading, the animation will be laggy - */ - this.setTimeout(() => { - this._animateBounceBack(ON_MOUNT_BOUNCE_DURATION); - }, ON_MOUNT_BOUNCE_DELAY); - } - }, - - UNSAFE_componentWillReceiveProps(nextProps: Object): void { - /** - * We do not need an "animateOpen(noCallback)" because this animation is - * handled internally by this component. - */ - if (this.props.isOpen && !nextProps.isOpen) { - this._animateToClosedPosition(); - } - }, - - render(): React.Element { - // The view hidden behind the main view - let slideOutView; - if (this.state.isSwipeableViewRendered && this.state.rowHeight) { - slideOutView = ( - - {this.props.slideoutView} - - ); - } - - // The swipeable item - const swipeableView = ( - - {this.props.children} - - ); - - return ( - - {slideOutView} - {swipeableView} - - ); - }, - - close(): void { - this.props.onClose(); - this._animateToClosedPosition(); - }, - - _onSwipeableViewLayout(event: Object): void { - this.setState({ - isSwipeableViewRendered: true, - rowHeight: event.nativeEvent.layout.height, - }); - }, - - _handleMoveShouldSetPanResponderCapture( - event: Object, - gestureState: Object, - ): boolean { - // Decides whether a swipe is responded to by this component or its child - return gestureState.dy < 10 && this._isValidSwipe(gestureState); - }, - - _handlePanResponderGrant(event: Object, gestureState: Object): void { - - }, - - _handlePanResponderMove(event: Object, gestureState: Object): void { - if (this._isSwipingExcessivelyRightFromClosedPosition(gestureState)) { - return; - } - - this.props.onSwipeStart(); - - if (this._isSwipingRightFromClosed(gestureState)) { - this._swipeSlowSpeed(gestureState); - } else { - this._swipeFullSpeed(gestureState); - } - }, - - _isSwipingRightFromClosed(gestureState: Object): boolean { - const gestureStateDx = isRTL() ? -gestureState.dx : gestureState.dx; - return this._previousLeft === CLOSED_LEFT_POSITION && gestureStateDx > 0; - }, - - _swipeFullSpeed(gestureState: Object): void { - this.state.currentLeft.setValue(this._previousLeft + gestureState.dx); - }, - - _swipeSlowSpeed(gestureState: Object): void { - this.state.currentLeft.setValue( - this._previousLeft + gestureState.dx / SLOW_SPEED_SWIPE_FACTOR, - ); - }, - - _isSwipingExcessivelyRightFromClosedPosition(gestureState: Object): boolean { - /** - * We want to allow a BIT of right swipe, to allow users to know that - * swiping is available, but swiping right does not do anything - * functionally. - */ - const gestureStateDx = isRTL() ? -gestureState.dx : gestureState.dx; - return ( - this._isSwipingRightFromClosed(gestureState) && - gestureStateDx > RIGHT_SWIPE_THRESHOLD - ); - }, - - _onPanResponderTerminationRequest( - event: Object, - gestureState: Object, - ): boolean { - return false; - }, - - _animateTo( - toValue: number, - duration: number = SWIPE_DURATION, - callback: Function = emptyFunction, - ): void { - Animated.timing( - this.state.currentLeft, - { - duration, - toValue, - useNativeDriver: true, - }, - ).start(() => { - this._previousLeft = toValue; - callback(); - }); - }, - - _animateToOpenPosition(): void { - const maxSwipeDistance = isRTL() ? -this.props.maxSwipeDistance : this.props.maxSwipeDistance; - this._animateTo(-maxSwipeDistance); - }, - - _animateToOpenPositionWith( - speed: number, - distMoved: number, - ): void { - /** - * Ensure the speed is at least the set speed threshold to prevent a slow - * swiping animation - */ - speed = ( - speed > HORIZONTAL_FULL_SWIPE_SPEED_THRESHOLD ? - speed : - HORIZONTAL_FULL_SWIPE_SPEED_THRESHOLD - ); - /** - * Calculate the duration the row should take to swipe the remaining distance - * at the same speed the user swiped (or the speed threshold) - */ - const duration = Math.abs((this.props.maxSwipeDistance - Math.abs(distMoved)) / speed); - const maxSwipeDistance = isRTL() ? -this.props.maxSwipeDistance : this.props.maxSwipeDistance; - this._animateTo(-maxSwipeDistance, duration); - }, - - _animateToClosedPosition(duration: number = SWIPE_DURATION): void { - this._animateTo(CLOSED_LEFT_POSITION, duration); - }, - - _animateToClosedPositionDuringBounce(): void { - this._animateToClosedPosition(RIGHT_SWIPE_BOUNCE_BACK_DURATION); - }, - - _animateBounceBack(duration: number): void { - /** - * When swiping right, we want to bounce back past closed position on release - * so users know they should swipe right to get content. - */ - const swipeBounceBackDistance = isRTL() ? - -RIGHT_SWIPE_BOUNCE_BACK_DISTANCE : - RIGHT_SWIPE_BOUNCE_BACK_DISTANCE; - this._animateTo( - -swipeBounceBackDistance, - duration, - this._animateToClosedPositionDuringBounce, - ); - }, - - // Ignore swipes due to user's finger moving slightly when tapping - _isValidSwipe(gestureState: Object): boolean { - if (this.props.preventSwipeRight && this._previousLeft === CLOSED_LEFT_POSITION && gestureState.dx > 0) { - return false; - } - - return Math.abs(gestureState.dx) > HORIZONTAL_SWIPE_DISTANCE_THRESHOLD; - }, - - _shouldAnimateRemainder(gestureState: Object): boolean { - /** - * If user has swiped past a certain distance, animate the rest of the way - * if they let go - */ - return ( - Math.abs(gestureState.dx) > this.props.swipeThreshold || - gestureState.vx > HORIZONTAL_FULL_SWIPE_SPEED_THRESHOLD - ); - }, - - _handlePanResponderEnd(event: Object, gestureState: Object): void { - const horizontalDistance = isRTL() ? -gestureState.dx : gestureState.dx; - if (this._isSwipingRightFromClosed(gestureState)) { - this.props.onOpen(); - this._animateBounceBack(RIGHT_SWIPE_BOUNCE_BACK_DURATION); - } else if (this._shouldAnimateRemainder(gestureState)) { - if (horizontalDistance < 0) { - // Swiped left - this.props.onOpen(); - this._animateToOpenPositionWith(gestureState.vx, horizontalDistance); - } else { - // Swiped right - this.props.onClose(); - this._animateToClosedPosition(); - } - } else { - if (this._previousLeft === CLOSED_LEFT_POSITION) { - this._animateToClosedPosition(); - } else { - this._animateToOpenPosition(); - } - } - - this.props.onSwipeEnd(); - }, -}); - -const styles = StyleSheet.create({ - slideOutContainer: { - bottom: 0, - left: 0, - position: 'absolute', - right: 0, - top: 0, - }, -}); - -export default SwipeableRow; diff --git a/packages/react-native-web/src/vendor/react-native/TouchHistoryMath/index.js b/packages/react-native-web/src/vendor/react-native/TouchHistoryMath/index.js index e56cc93b..8356ba14 100644 --- a/packages/react-native-web/src/vendor/react-native/TouchHistoryMath/index.js +++ b/packages/react-native-web/src/vendor/react-native/TouchHistoryMath/index.js @@ -1,4 +1,13 @@ -var TouchHistoryMath = { +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +const TouchHistoryMath = { /** * This code is optimized and not intended to look beautiful. This allows * computing of touch centroids that have moved after `touchesChangedAfter` @@ -19,13 +28,13 @@ var TouchHistoryMath = { touchHistory, touchesChangedAfter, isXAxis, - ofCurrent + ofCurrent, ) { - var touchBank = touchHistory.touchBank; - var total = 0; - var count = 0; + const touchBank = touchHistory.touchBank; + let total = 0; + let count = 0; - var oneTouchData = + const oneTouchData = touchHistory.numberActiveTouches === 1 ? touchHistory.touchBank[touchHistory.indexOfSingleActiveTouch] : null; @@ -39,22 +48,22 @@ var TouchHistoryMath = { ofCurrent && isXAxis ? oneTouchData.currentPageX : ofCurrent && !isXAxis - ? oneTouchData.currentPageY - : !ofCurrent && isXAxis - ? oneTouchData.previousPageX - : oneTouchData.previousPageY; + ? oneTouchData.currentPageY + : !ofCurrent && isXAxis + ? oneTouchData.previousPageX + : oneTouchData.previousPageY; count = 1; } } else { - for (var i = 0; i < touchBank.length; i++) { - var touchTrack = touchBank[i]; + for (let i = 0; i < touchBank.length; i++) { + const touchTrack = touchBank[i]; if ( touchTrack !== null && touchTrack !== undefined && touchTrack.touchActive && touchTrack.currentTimeStamp >= touchesChangedAfter ) { - var toAdd = void 0; // Yuck, program temporarily in invalid state. + let toAdd; // Yuck, program temporarily in invalid state. if (ofCurrent && isXAxis) { toAdd = touchTrack.currentPageX; } else if (ofCurrent && !isXAxis) { @@ -74,49 +83,49 @@ var TouchHistoryMath = { currentCentroidXOfTouchesChangedAfter: function( touchHistory, - touchesChangedAfter + touchesChangedAfter, ) { return TouchHistoryMath.centroidDimension( touchHistory, touchesChangedAfter, true, // isXAxis - true + true, // ofCurrent ); }, currentCentroidYOfTouchesChangedAfter: function( touchHistory, - touchesChangedAfter + touchesChangedAfter, ) { return TouchHistoryMath.centroidDimension( touchHistory, touchesChangedAfter, false, // isXAxis - true + true, // ofCurrent ); }, previousCentroidXOfTouchesChangedAfter: function( touchHistory, - touchesChangedAfter + touchesChangedAfter, ) { return TouchHistoryMath.centroidDimension( touchHistory, touchesChangedAfter, true, // isXAxis - false + false, // ofCurrent ); }, previousCentroidYOfTouchesChangedAfter: function( touchHistory, - touchesChangedAfter + touchesChangedAfter, ) { return TouchHistoryMath.centroidDimension( touchHistory, touchesChangedAfter, false, // isXAxis - false + false, // ofCurrent ); }, @@ -125,7 +134,7 @@ var TouchHistoryMath = { touchHistory, 0, // touchesChangedAfter true, // isXAxis - true + true, // ofCurrent ); }, @@ -134,11 +143,11 @@ var TouchHistoryMath = { touchHistory, 0, // touchesChangedAfter false, // isXAxis - true + true, // ofCurrent ); }, - noCentroid: -1 + noCentroid: -1, }; export default TouchHistoryMath; diff --git a/packages/react-native-web/src/vendor/react-native/Types/CoreEventTypes.js b/packages/react-native-web/src/vendor/react-native/Types/CoreEventTypes.js new file mode 100644 index 00000000..abe3ca17 --- /dev/null +++ b/packages/react-native-web/src/vendor/react-native/Types/CoreEventTypes.js @@ -0,0 +1,137 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + */ + +'use strict'; + +export type SyntheticEvent = $ReadOnly<{| + bubbles: ?boolean, + cancelable: ?boolean, + currentTarget: number, + defaultPrevented: ?boolean, + dispatchConfig: $ReadOnly<{| + registrationName: string, + |}>, + eventPhase: ?number, + preventDefault: () => void, + isDefaultPrevented: () => boolean, + stopPropagation: () => void, + isPropagationStopped: () => boolean, + isTrusted: ?boolean, + nativeEvent: T, + persist: () => void, + target: ?number, + timeStamp: number, + type: ?string, +|}>; + +export type ResponderSyntheticEvent = $ReadOnly<{| + ...SyntheticEvent, + touchHistory: $ReadOnly<{| + indexOfSingleActiveTouch: number, + mostRecentTimeStamp: number, + numberActiveTouches: number, + touchBank: $ReadOnlyArray< + $ReadOnly<{| + touchActive: boolean, + startPageX: number, + startPageY: number, + startTimeStamp: number, + currentPageX: number, + currentPageY: number, + currentTimeStamp: number, + previousPageX: number, + previousPageY: number, + previousTimeStamp: number, + |}>, + >, + |}>, +|}>; + +export type Layout = $ReadOnly<{| + x: number, + y: number, + width: number, + height: number, +|}>; + +export type TextLayout = $ReadOnly<{| + ...Layout, + ascender: number, + capHeight: number, + descender: number, + text: string, + xHeight: number, +|}>; + +export type LayoutEvent = SyntheticEvent< + $ReadOnly<{| + layout: Layout, + |}>, +>; + +export type TextLayoutEvent = SyntheticEvent< + $ReadOnly<{| + lines: Array, + |}>, +>; + +export type PressEvent = ResponderSyntheticEvent< + $ReadOnly<{| + changedTouches: $ReadOnlyArray<$PropertyType>, + force: number, + identifier: number, + locationX: number, + locationY: number, + pageX: number, + pageY: number, + target: ?number, + timestamp: number, + touches: $ReadOnlyArray<$PropertyType>, + |}>, +>; + +export type ScrollEvent = SyntheticEvent< + $ReadOnly<{| + contentInset: $ReadOnly<{| + bottom: number, + left: number, + right: number, + top: number, + |}>, + contentOffset: $ReadOnly<{| + y: number, + x: number, + |}>, + contentSize: $ReadOnly<{| + height: number, + width: number, + |}>, + layoutMeasurement: $ReadOnly<{| + height: number, + width: number, + |}>, + targetContentOffset?: $ReadOnly<{| + y: number, + x: number, + |}>, + velocity?: $ReadOnly<{| + y: number, + x: number, + |}>, + zoomScale?: number, + responderIgnoreScroll?: boolean, + |}>, +>; + +export type SwitchChangeEvent = SyntheticEvent< + $ReadOnly<{| + value: boolean, + |}>, +>; diff --git a/packages/react-native-web/src/vendor/react-native/ViewabilityHelper/index.js b/packages/react-native-web/src/vendor/react-native/ViewabilityHelper/index.js index 45a81f17..d11c8b76 100644 --- a/packages/react-native-web/src/vendor/react-native/ViewabilityHelper/index.js +++ b/packages/react-native-web/src/vendor/react-native/ViewabilityHelper/index.js @@ -1,12 +1,13 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @noflow + * @flow * @format */ +'use strict'; import invariant from 'fbjs/lib/invariant'; @@ -120,10 +121,13 @@ class ViewabilityHelper { } let firstVisible = -1; const {first, last} = renderRange || {first: 0, last: itemCount - 1}; - invariant( - last < itemCount, - 'Invalid render range ' + JSON.stringify({renderRange, itemCount}), - ); + if (last >= itemCount) { + console.warn( + 'Invalid render range computing viewability ' + + JSON.stringify({renderRange, itemCount}), + ); + return []; + } for (let idx = first; idx <= last; idx++) { const metrics = getFrameMetrics(idx); if (!metrics) { diff --git a/packages/react-native-web/src/vendor/react-native/VirtualizeUtils/index.js b/packages/react-native-web/src/vendor/react-native/VirtualizeUtils/index.js index f061d5e5..2c497746 100644 --- a/packages/react-native-web/src/vendor/react-native/VirtualizeUtils/index.js +++ b/packages/react-native-web/src/vendor/react-native/VirtualizeUtils/index.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. @@ -7,6 +7,7 @@ * @flow * @format */ +'use strict'; import invariant from 'fbjs/lib/invariant'; @@ -206,16 +207,16 @@ function computeWindowedRenderLimits( return {first, last}; } -export { - computeWindowedRenderLimits, - elementsThatOverlapOffsets, - newRangeCount -} - const VirtualizeUtils = { computeWindowedRenderLimits, elementsThatOverlapOffsets, - newRangeCount + newRangeCount, }; +export { + computeWindowedRenderLimits, + elementsThatOverlapOffsets, + newRangeCount, +} + export default VirtualizeUtils; diff --git a/packages/react-native-web/src/vendor/react-native/VirtualizedList/index.js b/packages/react-native-web/src/vendor/react-native/VirtualizedList/index.js index 7a2b1d0d..e92eb1e7 100644 --- a/packages/react-native-web/src/vendor/react-native/VirtualizedList/index.js +++ b/packages/react-native-web/src/vendor/react-native/VirtualizedList/index.js @@ -1,41 +1,46 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @noflow + * @flow * @format */ +'use strict'; + import Batchinator from '../Batchinator'; import FillRateHelper from '../FillRateHelper'; import PropTypes from 'prop-types'; -import React from 'react'; +import * as React from 'react'; import RefreshControl from '../../../exports/RefreshControl'; import ScrollView from '../../../exports/ScrollView'; -import StyleSheet, { type StyleObj } from '../../../exports/StyleSheet'; +import StyleSheet from '../../../exports/StyleSheet'; import UIManager from '../../../exports/UIManager'; import View from '../../../exports/View'; -import ViewabilityHelper, { - type ViewabilityConfig, - type ViewToken, - type ViewabilityConfigCallbackPair -} from '../ViewabilityHelper'; -import { computeWindowedRenderLimits } from '../VirtualizeUtils'; +import ViewabilityHelper from '../ViewabilityHelper'; + import findNodeHandle from '../../../exports/findNodeHandle'; +const flattenStyle = StyleSheet.flatten; import infoLog from '../infoLog'; import invariant from 'fbjs/lib/invariant'; import warning from 'fbjs/lib/warning'; +import { computeWindowedRenderLimits } from '../VirtualizeUtils'; -const flattenStyle = StyleSheet.flatten; -const __DEV__ = process.env.NODE_ENV !== 'production'; - -type DangerouslyImpreciseStyleProp = any; +import type {ViewProps} from '../../../exports/View/ViewPropTypes'; +import type { + ViewabilityConfig, + ViewToken, + ViewabilityConfigCallbackPair, +} from '../ViewabilityHelper'; type Item = any; +type ViewStyleProp = $PropertyType; export type renderItemType = (info: any) => ?React.Element; +const __DEV__ = process.env.NODE_ENV !== 'production'; + type ViewabilityHelperCallbackTuple = { viewabilityHelper: ViewabilityHelper, onViewableItemsChanged: (info: { @@ -49,7 +54,7 @@ type RequiredProps = { // `VirtualizedSectionList`'s props. renderItem: $FlowFixMe, /** - * The default accessor functions assume this is an Array<{key: string}> but you can override + * The default accessor functions assume this is an Array<{key: string} | {id: string}> but you can override * getItem, getItemCount, and keyExtractor to handle any type of index-based data. */ data?: any, @@ -73,7 +78,7 @@ type OptionalProps = { * unmounts react instances that are outside of the render window. You should only need to disable * this for debugging purposes. */ - disableVirtualization: boolean, + disableVirtualization?: ?boolean, /** * A marker property for telling the list to re-render (since it implements `PureComponent`). If * any of your `renderItem`, Header, Footer, etc. functions depend on anything outside of the @@ -118,11 +123,19 @@ type OptionalProps = { * a rendered element. */ ListFooterComponent?: ?(React.ComponentType | React.Element), + /** + * Styling for internal View for ListFooterComponent + */ + ListFooterComponentStyle?: ViewStyleProp, /** * Rendered at the top of all the items. Can be a React Component Class, a render function, or * a rendered element. */ ListHeaderComponent?: ?(React.ComponentType | React.Element), + /** + * Styling for internal View for ListHeaderComponent + */ + ListHeaderComponentStyle?: ViewStyleProp, /** * A unique identifier for this list. If there are multiple VirtualizedLists at the same level of * nesting within another VirtualizedList, this key is necessary for virtualization to @@ -161,11 +174,18 @@ type OptionalProps = { viewableItems: Array, changed: Array, }) => void, + persistentScrollbar?: ?boolean, /** * Set this when offset is needed for the loading indicator to show correctly. * @platform android */ progressViewOffset?: number, + /** + * A custom refresh control element. When set, it overrides the default + * component built internally. The onRefresh and refreshing + * props are also ignored. Only works for vertical VirtualizedList. + */ + refreshControl?: ?React.Element, /** * Set this true while waiting for new data from a refresh. */ @@ -205,6 +225,7 @@ type OptionalProps = { export type Props = RequiredProps & OptionalProps; let _usedIndexForKey = false; +let _keylessItemComponentName: string = ''; type Frame = { offset: number, @@ -245,7 +266,7 @@ type State = {first: number, last: number}; * offscreen. This means it's possible to scroll faster than the fill rate ands momentarily see * blank content. This is a tradeoff that can be adjusted to suit the needs of each application, * and we are working on improving it behind the scenes. - * - By default, the list looks for a `key` prop on each item and uses that for the React key. + * - By default, the list looks for a `key` or `id` prop on each item and uses that for the React key. * Alternatively, you can provide a custom `keyExtractor` prop. * */ @@ -268,9 +289,6 @@ class VirtualizedList extends React.PureComponent { * suppresses an error when upgrading Flow's support for React. To see the * error delete this comment and run Flow. */ this._scrollRef.scrollTo( - /* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This - * comment suppresses an error when upgrading Flow's support for React. - * To see the error delete this comment and run Flow. */ this.props.horizontal ? {x: offset, animated} : {y: offset, animated}, ); } @@ -319,9 +337,6 @@ class VirtualizedList extends React.PureComponent { * suppresses an error when upgrading Flow's support for React. To see the * error delete this comment and run Flow. */ this._scrollRef.scrollTo( - /* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This - * comment suppresses an error when upgrading Flow's support for React. - * To see the error delete this comment and run Flow. */ horizontal ? {x: offset, animated} : {y: offset, animated}, ); } @@ -360,9 +375,6 @@ class VirtualizedList extends React.PureComponent { * suppresses an error when upgrading Flow's support for React. To see the * error delete this comment and run Flow. */ this._scrollRef.scrollTo( - /* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This - * comment suppresses an error when upgrading Flow's support for React. - * To see the error delete this comment and run Flow. */ this.props.horizontal ? {x: offset, animated} : {y: offset, animated}, ); } @@ -403,6 +415,14 @@ class VirtualizedList extends React.PureComponent { } } + getScrollRef() { + if (this._scrollRef && this._scrollRef.getScrollRef) { + return this._scrollRef.getScrollRef(); + } else { + return this._scrollRef; + } + } + setNativeProps(props: Object) { if (this._scrollRef) { this._scrollRef.setNativeProps(props); @@ -410,14 +430,20 @@ class VirtualizedList extends React.PureComponent { } static defaultProps = { - disableVirtualization: process.env.NODE_ENV === 'test', + disableVirtualization: false, horizontal: false, initialNumToRender: 10, keyExtractor: (item: Item, index: number) => { if (item.key != null) { return item.key; } + if (item.id != null) { + return item.id; + } _usedIndexForKey = true; + if (item.type && item.type.displayName) { + _keylessItemComponentName = item.type.displayName; + } return String(index); }, maxToRenderPerBatch: 10, @@ -505,12 +531,13 @@ class VirtualizedList extends React.PureComponent { this._cellKeysToChildListKeys.set(childList.cellKey, childListsInCell); const existingChildData = this._nestedChildLists.get(childList.key); - invariant( - !(existingChildData && existingChildData.ref !== null), - 'A VirtualizedList contains a cell which itself contains ' + - 'more than one VirtualizedList of the same orientation as the parent ' + - 'list. You must pass a unique listKey prop to each sibling list.', - ); + if (existingChildData && existingChildData.ref !== null) { + console.error( + 'A VirtualizedList contains a cell which itself contains ' + + 'more than one VirtualizedList of the same orientation as the parent ' + + 'list. You must pass a unique listKey prop to each sibling list.', + ); + } this._nestedChildLists.set(childList.key, { ref: childList.ref, state: null, @@ -619,7 +646,7 @@ class VirtualizedList extends React.PureComponent { } static getDerivedStateFromProps(newProps: Props, prevState: State) { - const {data, extraData, getItemCount, maxToRenderPerBatch} = newProps; + const {data, getItemCount, maxToRenderPerBatch} = newProps; // first and last could be stale (e.g. if a new, shorter items props is passed in), so we make // sure we're rendering a reasonable range here. return { @@ -637,7 +664,7 @@ class VirtualizedList extends React.PureComponent { stickyIndicesFromProps: Set, first: number, last: number, - inversionStyle: ?DangerouslyImpreciseStyleProp, + inversionStyle: ViewStyleProp, ) { const { CellRendererComponent, @@ -692,7 +719,7 @@ class VirtualizedList extends React.PureComponent { }; _isVirtualizationDisabled(): boolean { - return this.props.disableVirtualization; + return this.props.disableVirtualization || false; } _isNestedWithSameOrientation(): boolean { @@ -740,12 +767,16 @@ class VirtualizedList extends React.PureComponent { - - {/* - Flow doesn't know this is a React.Element and not a React.Component - $FlowFixMe https://fburl.com/b9xmtm09 - */} - {element} + + { + // $FlowFixMe - Typing ReactNativeComponent revealed errors + element + } , ); @@ -753,6 +784,7 @@ class VirtualizedList extends React.PureComponent { const itemCount = this.props.getItemCount(data); if (itemCount > 0) { _usedIndexForKey = false; + _keylessItemComponentName = ''; const spacerKey = !horizontal ? 'height' : 'width'; const lastInitialIndex = this.props.initialScrollIndex ? -1 @@ -777,7 +809,9 @@ class VirtualizedList extends React.PureComponent { const initBlock = this._getFrameMetricsApprox(lastInitialIndex); const stickyBlock = this._getFrameMetricsApprox(ii); const leadSpace = - stickyBlock.offset - (initBlock.offset + initBlock.length); + stickyBlock.offset - + initBlock.offset - + (this.props.initialScrollIndex ? 0 : initBlock.length); cells.push( , ); @@ -820,8 +854,9 @@ class VirtualizedList extends React.PureComponent { ); if (!this._hasWarned.keys && _usedIndexForKey) { console.warn( - 'VirtualizedList: missing keys for items, make sure to specify a key property on each ' + + 'VirtualizedList: missing keys for items, make sure to specify a key or id property on each ' + 'item or provide a custom keyExtractor.', + _keylessItemComponentName, ); this._hasWarned.keys = true; } @@ -843,23 +878,28 @@ class VirtualizedList extends React.PureComponent { ); } } else if (ListEmptyComponent) { - const element = React.isValidElement(ListEmptyComponent) ? ( + const element: React.Element = ((React.isValidElement( + ListEmptyComponent, + ) ? ( ListEmptyComponent ) : ( // $FlowFixMe - ); + )): any); cells.push( - - {/* - Flow doesn't know this is a React.Element and not a React.Component - $FlowFixMe https://fburl.com/b9xmtm09 - */} - {element} - , + React.cloneElement(element, { + key: '$empty', + onLayout: event => { + this._onLayoutEmpty(event); + if (element.props.onLayout) { + element.props.onLayout(event); + } + }, + style: StyleSheet.compose( + inversionStyle, + element.props.style, + ), + }), ); } if (ListFooterComponent) { @@ -873,12 +913,16 @@ class VirtualizedList extends React.PureComponent { - - {/* - Flow doesn't know this is a React.Element and not a React.Component - $FlowFixMe https://fburl.com/b9xmtm09 - */} - {element} + + { + // $FlowFixMe - Typing ReactNativeComponent revealed errors + element + } , ); @@ -892,12 +936,16 @@ class VirtualizedList extends React.PureComponent { onScrollEndDrag: this._onScrollEndDrag, onMomentumScrollEnd: this._onMomentumScrollEnd, scrollEventThrottle: this.props.scrollEventThrottle, // TODO: Android support - invertStickyHeaders: this.props.invertStickyHeaders !== undefined - ? this.props.invertStickyHeaders - : this.props.inverted, + invertStickyHeaders: + this.props.invertStickyHeaders !== undefined + ? this.props.invertStickyHeaders + : this.props.inverted, stickyHeaderIndices, }; if (inversionStyle) { + /* $FlowFixMe(>=0.70.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.70 was deployed. To see the error delete + * this comment and run Flow. */ scrollProps.style = [inversionStyle, this.props.style]; } @@ -915,7 +963,7 @@ class VirtualizedList extends React.PureComponent { ); if (this.props.debug) { return ( - + {ret} {this._renderDebugOverlay()} @@ -936,7 +984,19 @@ class VirtualizedList extends React.PureComponent { tuple.viewabilityHelper.resetViewableIndices(); }); } + // The `this._hiPriInProgress` is guaranteeing a hiPri cell update will only happen + // once per fiber update. The `_scheduleCellsToRenderUpdate` will set it to true + // if a hiPri update needs to perform. If `componentDidUpdate` is triggered with + // `this._hiPriInProgress=true`, means it's triggered by the hiPri update. The + // `_scheduleCellsToRenderUpdate` will check this condition and not perform + // another hiPri update. + const hiPriInProgress = this._hiPriInProgress; this._scheduleCellsToRenderUpdate(); + // Make sure setting `this._hiPriInProgress` back to false after `componentDidUpdate` + // is triggered with `this._hiPriInProgress = true` + if (hiPriInProgress) { + this._hiPriInProgress = false; + } } _averageCellLength = 0; @@ -947,13 +1007,14 @@ class VirtualizedList extends React.PureComponent { _frames = {}; _footerLength = 0; _hasDataChangedSinceEndReached = true; + _hasDoneInitialScroll = false; _hasInteracted = false; _hasMore = false; _hasWarned = {}; - _highestMeasuredFrameIndex = 0; _headerLength = 0; + _hiPriInProgress: boolean = false; // flag to prevent infinite hiPri cell limit update + _highestMeasuredFrameIndex = 0; _indicesToKeys: Map = new Map(); - _hasDoneInitialScroll = false; _nestedChildLists: Map< string, {ref: ?VirtualizedList, state: ?ChildListState}, @@ -989,9 +1050,11 @@ class VirtualizedList extends React.PureComponent { } _defaultRenderScrollComponent = props => { + const onRefresh = props.onRefresh; if (this._isNestedWithSameOrientation()) { + // $FlowFixMe - Typing ReactNativeComponent revealed errors return ; - } else if (props.onRefresh) { + } else if (onRefresh) { invariant( typeof props.refreshing === 'boolean', '`refreshing` prop must be set as a boolean in order to use `onRefresh`, but got `' + @@ -999,21 +1062,24 @@ class VirtualizedList extends React.PureComponent { '`', ); return ( + // $FlowFixMe Invalid prop usage =0.53.0 site=react_native_fb,react_native_oss) This - * comment suppresses an error when upgrading Flow's support for - * React. To see the error delete this comment and run Flow. */ - + props.refreshControl == null ? ( + + ) : ( + props.refreshControl + ) } /> ); } else { + // $FlowFixMe Invalid prop usage return ; } }; @@ -1046,7 +1112,19 @@ class VirtualizedList extends React.PureComponent { } else { this._frames[cellKey].inLayout = true; } + + const childListKeys = this._cellKeysToChildListKeys.get(cellKey); + if (childListKeys) { + for (let childKey of childListKeys) { + const childList = this._nestedChildLists.get(childKey); + childList && + childList.ref && + childList.ref.measureLayoutRelativeToContainingList(); + } + } + this._computeBlankness(); + this._updateViewableItems(this.props.data); } _onCellUnmount = (cellKey: string) => { @@ -1056,36 +1134,52 @@ class VirtualizedList extends React.PureComponent { } }; - _measureLayoutRelativeToContainingList(): void { - UIManager.measureLayout( - findNodeHandle(this), - findNodeHandle( - this.context.virtualizedList.getOutermostParentListRef(), - ), - error => { - console.warn( - "VirtualizedList: Encountered an error while measuring a list's" + - ' offset from its containing VirtualizedList.', - ); - }, - (x, y, width, height) => { - this._offsetFromParentVirtualizedList = this._selectOffset({x, y}); - this._scrollMetrics.contentLength = this._selectLength({width, height}); - - const scrollMetrics = this._convertParentScrollMetrics( - this.context.virtualizedList.getScrollMetrics(), - ); - this._scrollMetrics.visibleLength = scrollMetrics.visibleLength; - this._scrollMetrics.offset = scrollMetrics.offset; - }, - ); + measureLayoutRelativeToContainingList(): void { + // TODO (T35574538): findNodeHandle sometimes crashes with "Unable to find + // node on an unmounted component" during scrolling + try { + if (!this._scrollRef) { + return; + } + // We are asuming that getOutermostParentListRef().getScrollRef() + // is a non-null reference to a ScrollView + this._scrollRef.measureLayout( + this.context.virtualizedList + .getOutermostParentListRef() + .getScrollRef() + .getNativeScrollRef(), + (x, y, width, height) => { + this._offsetFromParentVirtualizedList = this._selectOffset({x, y}); + this._scrollMetrics.contentLength = this._selectLength({ + width, + height, + }); + const scrollMetrics = this._convertParentScrollMetrics( + this.context.virtualizedList.getScrollMetrics(), + ); + this._scrollMetrics.visibleLength = scrollMetrics.visibleLength; + this._scrollMetrics.offset = scrollMetrics.offset; + }, + error => { + console.warn( + "VirtualizedList: Encountered an error while measuring a list's" + + ' offset from its containing VirtualizedList.', + ); + }, + ); + } catch (error) { + console.warn( + 'measureLayoutRelativeToContainingList threw an error', + error.stack, + ); + } } _onLayout = (e: Object) => { if (this._isNestedWithSameOrientation()) { // Need to adjust our scroll metrics to be relative to our containing // VirtualizedList before we can make claims about list item viewability - this._measureLayoutRelativeToContainingList(); + this.measureLayoutRelativeToContainingList(); } else { this._scrollMetrics.visibleLength = this._selectLength( e.nativeEvent.layout, @@ -1110,11 +1204,15 @@ class VirtualizedList extends React.PureComponent { _renderDebugOverlay() { const normalize = - this._scrollMetrics.visibleLength / this._scrollMetrics.contentLength; + this._scrollMetrics.visibleLength / + (this._scrollMetrics.contentLength || 1); const framesInLayout = []; const itemCount = this.props.getItemCount(this.props.data); for (let ii = 0; ii < itemCount; ii++) { const frame = this._getFrameMetricsApprox(ii); + /* $FlowFixMe(>=0.68.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.68 was deployed. To see the error delete this + * comment and run Flow. */ if (frame.inLayout) { framesInLayout.push(frame); } @@ -1124,47 +1222,41 @@ class VirtualizedList extends React.PureComponent { const windowLen = frameLast.offset + frameLast.length - windowTop; const visTop = this._scrollMetrics.offset; const visLen = this._scrollMetrics.visibleLength; - const baseStyle = {position: 'absolute', top: 0, right: 0}; + return ( - + {framesInLayout.map((f, ii) => ( ))} ); @@ -1323,18 +1415,26 @@ class VirtualizedList extends React.PureComponent { const {offset, visibleLength, velocity} = this._scrollMetrics; const itemCount = this.props.getItemCount(this.props.data); let hiPri = false; - if (first > 0 || last < itemCount - 1) { + const scrollingThreshold = + /* $FlowFixMe(>=0.63.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.63 was deployed. To see the error delete + * this comment and run Flow. */ + (this.props.onEndReachedThreshold * visibleLength) / 2; + // Mark as high priority if we're close to the start of the first item + // But only if there are items before the first rendered item + if (first > 0) { const distTop = offset - this._getFrameMetricsApprox(first).offset; + hiPri = + hiPri || distTop < 0 || (velocity < -2 && distTop < scrollingThreshold); + } + // Mark as high priority if we're close to the end of the last item + // But only if there are items after the last rendered item + if (last < itemCount - 1) { const distBottom = this._getFrameMetricsApprox(last).offset - (offset + visibleLength); - const scrollingThreshold = - /* $FlowFixMe(>=0.63.0 site=react_native_fb) This comment suppresses an - * error found when Flow v0.63 was deployed. To see the error delete - * this comment and run Flow. */ - this.props.onEndReachedThreshold * visibleLength / 2; hiPri = - Math.min(distTop, distBottom) < 0 || - (velocity < -2 && distTop < scrollingThreshold) || + hiPri || + distBottom < 0 || (velocity > 2 && distBottom < scrollingThreshold); } // Only trigger high-priority updates if we've actually rendered cells, @@ -1342,7 +1442,14 @@ class VirtualizedList extends React.PureComponent { // Otherwise, it would just render as many cells as it can (of zero dimension), // each time through attempting to render more (limited by maxToRenderPerBatch), // starving the renderer from actually laying out the objects and computing _averageCellLength. - if (hiPri && this._averageCellLength) { + // If this is triggered in an `componentDidUpdate` followed by a hiPri cellToRenderUpdate + // We shouldn't do another hipri cellToRenderUpdate + if ( + hiPri && + (this._averageCellLength || this.props.getItemLayout) && + !this._hiPriInProgress + ) { + this._hiPriInProgress = true; // Don't worry about interactions when scrolling quickly; focus on filling content as fast // as possible. this._updateCellsToRenderBatcher.dispose({abort: true}); @@ -1552,7 +1659,7 @@ class CellRenderer extends React.Component< fillRateHelper: FillRateHelper, horizontal: ?boolean, index: number, - inversionStyle: ?DangerouslyImpreciseStyleProp, + inversionStyle: ViewStyleProp, item: Item, onLayout: (event: Object) => void, // This is extracted by ScrollViewStickyHeader onUnmount: (cellKey: string) => void, @@ -1561,7 +1668,7 @@ class CellRenderer extends React.Component< getItemLayout?: ?Function, renderItem: renderItemType, }, - prevCellKey: ?string + prevCellKey: ?string, }, $FlowFixMeState, > { @@ -1639,6 +1746,9 @@ class CellRenderer extends React.Component< separators: this._separators, }); const onLayout = + /* $FlowFixMe(>=0.68.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.68 was deployed. To see the error delete this + * comment and run Flow. */ getItemLayout && !parentProps.debug && !fillRateHelper.enabled() ? undefined : this.props.onLayout; @@ -1651,9 +1761,14 @@ class CellRenderer extends React.Component< ? horizontal ? [styles.rowReverse, inversionStyle] : [styles.columnReverse, inversionStyle] - : horizontal ? [styles.row, inversionStyle] : inversionStyle; + : horizontal + ? [styles.row, inversionStyle] + : inversionStyle; if (!CellRendererComponent) { return ( + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete + * this comment and run Flow. */ {element} {itemSeparator} @@ -1710,7 +1825,35 @@ const styles = StyleSheet.create({ }, columnReverse: { flexDirection: 'column-reverse' - } + }, + debug: { + flex: 1, + }, + debugOverlayBase: { + position: 'absolute', + top: 0, + right: 0, + }, + debugOverlay: { + bottom: 0, + width: 20, + borderColor: 'blue', + borderWidth: 1, + }, + debugOverlayFrame: { + left: 0, + backgroundColor: 'orange', + }, + debugOverlayFrameLast: { + left: 0, + borderColor: 'green', + borderWidth: 2, + }, + debugOverlayFrameVis: { + left: 0, + borderColor: 'red', + borderWidth: 2, + }, }); export default VirtualizedList; diff --git a/packages/react-native-web/src/vendor/react-native/VirtualizedSectionList/index.js b/packages/react-native-web/src/vendor/react-native/VirtualizedSectionList/index.js index 8e213f63..de257221 100644 --- a/packages/react-native-web/src/vendor/react-native/VirtualizedSectionList/index.js +++ b/packages/react-native-web/src/vendor/react-native/VirtualizedSectionList/index.js @@ -1,60 +1,64 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @noflow + * @flow * @format */ +'use strict'; -import React from 'react'; +import * as React from 'react'; import View from '../../../exports/View'; -import VirtualizedList, { type Props as VirtualizedListProps } from '../VirtualizedList'; +import VirtualizedList from '../VirtualizedList'; + import invariant from 'fbjs/lib/invariant'; -import type { ViewToken } from '../ViewabilityHelper'; + +import type {ViewToken} from '../ViewabilityHelper'; +import type {Props as VirtualizedListProps} from '../VirtualizedList'; type Item = any; -type SectionItem = any; -type SectionBase = { - // Must be provided directly on each section. - data: $ReadOnlyArray, +export type SectionBase = { + /** + * The data for rendering items in this section. + */ + data: $ReadOnlyArray, + /** + * Optional key to keep track of section re-ordering. If you don't plan on re-ordering sections, + * the array index will be used by default. + */ key?: string, // Optional props will override list-wide props just for this section. - renderItem?: ?({ - item: SectionItem, + renderItem?: ?(info: { + item: SectionItemT, index: number, - section: SectionBase, + section: SectionBase, separators: { highlight: () => void, unhighlight: () => void, updateProps: (select: 'leading' | 'trailing', newProps: Object) => void, }, }) => ?React.Element, - ItemSeparatorComponent?: ?React.ComponentType<*>, - keyExtractor?: (item: SectionItem, index: ?number) => string, - - // TODO: support more optional/override props - // FooterComponent?: ?ReactClass<*>, - // HeaderComponent?: ?ReactClass<*>, - // onViewableItemsChanged?: ({viewableItems: Array, changed: Array}) => void, + ItemSeparatorComponent?: ?React.ComponentType, + keyExtractor?: (item: SectionItemT, index?: ?number) => string, }; -type RequiredProps = { +type RequiredProps> = { sections: $ReadOnlyArray, }; -type OptionalProps = { +type OptionalProps> = { /** * Rendered after the last item in the last section. */ - ListFooterComponent?: ?(React.ComponentType<*> | React.Element), + ListFooterComponent?: ?(React.ComponentType | React.Element), /** * Rendered at the very beginning of the list. */ - ListHeaderComponent?: ?(React.ComponentType<*> | React.Element), + ListHeaderComponent?: ?(React.ComponentType | React.Element), /** * Default renderer for every item in every section. */ @@ -80,24 +84,24 @@ type OptionalProps = { * Rendered at the bottom of every Section, except the very last one, in place of the normal * ItemSeparatorComponent. */ - SectionSeparatorComponent?: ?React.ComponentType<*>, + SectionSeparatorComponent?: ?React.ComponentType, /** * Rendered at the bottom of every Item except the very last one in the last section. */ - ItemSeparatorComponent?: ?React.ComponentType<*>, + ItemSeparatorComponent?: ?React.ComponentType, /** - * Warning: Virtualization can drastically improve memory consumption for long lists, but trashes - * the state of items when they scroll out of the render window, so make sure all relavent data is - * stored outside of the recursive `renderItem` instance tree. + * DEPRECATED: Virtualization provides significant performance and memory optimizations, but fully + * unmounts react instances that are outside of the render window. You should only need to disable + * this for debugging purposes. */ - enableVirtualization?: ?boolean, + disableVirtualization?: ?boolean, keyExtractor: (item: Item, index: number) => string, onEndReached?: ?({distanceFromEnd: number}) => void, /** * If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make * sure to also set the `refreshing` prop correctly. */ - onRefresh?: ?Function, + onRefresh?: ?() => void, /** * Called when the viewability of rows changes, as defined by the * `viewabilityConfig` prop. @@ -126,14 +130,9 @@ type State = {childProps: VirtualizedListProps}; * hood. The only operation that might not scale well is concatting the data arrays of all the * sections when new props are received, which should be plenty fast for up to ~10,000 items. */ -class VirtualizedSectionList extends React.PureComponent< - Props, - State, -> { - props: Props; - - state: State; - +class VirtualizedSectionList< + SectionT: SectionBase, +> extends React.PureComponent, State> { static defaultProps: DefaultProps = { ...VirtualizedList.defaultProps, data: [], @@ -145,12 +144,20 @@ class VirtualizedSectionList extends React.PureComponent< sectionIndex: number, viewPosition?: number, }) { - let index = params.itemIndex + 1; - for (let ii = 0; ii < params.sectionIndex; ii++) { - index += this.props.sections[ii].data.length + 2; + let index = params.itemIndex; + for (let i = 0; i < params.sectionIndex; i++) { + index += this.props.getItemCount(this.props.sections[i].data) + 2; + } + let viewOffset = 0; + if (params.itemIndex > 0 && this.props.stickySectionHeadersEnabled) { + const frame = this._listRef._getFrameMetricsApprox( + index - params.itemIndex, + ); + viewOffset = frame.length; } const toIndexParams = { ...params, + viewOffset, index, }; this._listRef.scrollToIndex(toIndexParams); @@ -160,6 +167,51 @@ class VirtualizedSectionList extends React.PureComponent< return this._listRef; } + constructor(props: Props, context: Object) { + super(props, context); + this.state = this._computeState(props); + } + + UNSAFE_componentWillReceiveProps(nextProps: Props) { + this.setState(this._computeState(nextProps)); + } + + _computeState(props: Props): State { + const offset = props.ListHeaderComponent ? 1 : 0; + const stickyHeaderIndices = []; + const itemCount = props.sections + ? props.sections.reduce((v, section) => { + stickyHeaderIndices.push(v + offset); + return v + props.getItemCount(section.data) + 2; // Add two for the section header and footer. + }, 0) + : 0; + + return { + childProps: { + ...props, + renderItem: this._renderItem, + ItemSeparatorComponent: undefined, // Rendered with renderItem + data: props.sections, + getItemCount: () => itemCount, + // $FlowFixMe + getItem: (sections, index) => getItem(props, sections, index), + keyExtractor: this._keyExtractor, + onViewableItemsChanged: props.onViewableItemsChanged + ? this._onViewableItemsChanged + : undefined, + stickyHeaderIndices: props.stickySectionHeadersEnabled + ? stickyHeaderIndices + : undefined, + }, + }; + } + + render() { + return ( + + ); + } + _keyExtractor = (item: Item, index: number) => { const info = this._subExtractor(index); return (info && info.key) || String(index); @@ -178,39 +230,41 @@ class VirtualizedSectionList extends React.PureComponent< trailingSection?: ?SectionT, } { let itemIndex = index; - const defaultKeyExtractor = this.props.keyExtractor; - for (let ii = 0; ii < this.props.sections.length; ii++) { - const section = this.props.sections[ii]; - const key = section.key || String(ii); + const {getItem, getItemCount, keyExtractor, sections} = this.props; + for (let i = 0; i < sections.length; i++) { + const section = sections[i]; + const sectionData = section.data; + const key = section.key || String(i); itemIndex -= 1; // The section adds an item for the header - if (itemIndex >= section.data.length + 1) { - itemIndex -= section.data.length + 1; // The section adds an item for the footer. + if (itemIndex >= getItemCount(sectionData) + 1) { + itemIndex -= getItemCount(sectionData) + 1; // The section adds an item for the footer. } else if (itemIndex === -1) { return { section, key: key + ':header', index: null, header: true, - trailingSection: this.props.sections[ii + 1], + trailingSection: sections[i + 1], }; - } else if (itemIndex === section.data.length) { + } else if (itemIndex === getItemCount(sectionData)) { return { section, key: key + ':footer', index: null, header: false, - trailingSection: this.props.sections[ii + 1], + trailingSection: sections[i + 1], }; } else { - const keyExtractor = section.keyExtractor || defaultKeyExtractor; + const extractor = section.keyExtractor || keyExtractor; return { section, - key: key + ':' + keyExtractor(section.data[itemIndex], itemIndex), + key: + key + ':' + extractor(getItem(sectionData, itemIndex), itemIndex), index: itemIndex, - leadingItem: section.data[itemIndex - 1], - leadingSection: this.props.sections[ii - 1], - trailingItem: section.data[itemIndex + 1], - trailingSection: this.props.sections[ii + 1], + leadingItem: getItem(sectionData, itemIndex - 1), + leadingSection: sections[i - 1], + trailingItem: getItem(sectionData, itemIndex + 1), + trailingSection: sections[i + 1], }; } } @@ -303,7 +357,7 @@ class VirtualizedSectionList extends React.PureComponent< _getSeparatorComponent( index: number, info?: ?Object, - ): ?React.ComponentType<*> { + ): ?React.ComponentType { info = info || this._subExtractor(index); if (!info) { return null; @@ -312,7 +366,8 @@ class VirtualizedSectionList extends React.PureComponent< info.section.ItemSeparatorComponent || this.props.ItemSeparatorComponent; const {SectionSeparatorComponent} = this.props; const isLastItemInList = index === this.state.childProps.getItemCount() - 1; - const isLastItemInSection = info.index === info.section.data.length - 1; + const isLastItemInSection = + info.index === this.props.getItemCount(info.section.data) - 1; if (SectionSeparatorComponent && isLastItemInSection) { return SectionSeparatorComponent; } @@ -322,48 +377,6 @@ class VirtualizedSectionList extends React.PureComponent< return null; } - _computeState(props: Props): State { - const offset = props.ListHeaderComponent ? 1 : 0; - const stickyHeaderIndices = []; - const itemCount = props.sections.reduce((v, section) => { - stickyHeaderIndices.push(v + offset); - return v + section.data.length + 2; // Add two for the section header and footer. - }, 0); - - return { - childProps: { - ...props, - renderItem: this._renderItem, - ItemSeparatorComponent: undefined, // Rendered with renderItem - data: props.sections, - getItemCount: () => itemCount, - getItem, - keyExtractor: this._keyExtractor, - onViewableItemsChanged: props.onViewableItemsChanged - ? this._onViewableItemsChanged - : undefined, - stickyHeaderIndices: props.stickySectionHeadersEnabled - ? stickyHeaderIndices - : undefined, - }, - }; - } - - constructor(props: Props, context: Object) { - super(props, context); - this.state = this._computeState(props); - } - - UNSAFE_componentWillReceiveProps(nextProps: Props) { - this.setState(this._computeState(nextProps)); - } - - render() { - return ( - - ); - } - _cellRefs = {}; _listRef: VirtualizedList; _captureRef = ref => { @@ -374,25 +387,40 @@ class VirtualizedSectionList extends React.PureComponent< }; } -type ItemWithSeparatorProps = { - LeadingSeparatorComponent: ?React.ComponentType<*>, - SeparatorComponent: ?React.ComponentType<*>, +type ItemWithSeparatorCommonProps = $ReadOnly<{| + leadingItem: ?Item, + leadingSection: ?Object, + section: Object, + trailingItem: ?Item, + trailingSection: ?Object, +|}>; + +type ItemWithSeparatorProps = $ReadOnly<{| + ...ItemWithSeparatorCommonProps, + LeadingSeparatorComponent: ?React.ComponentType, + SeparatorComponent: ?React.ComponentType, cellKey: string, index: number, item: Item, onUpdateSeparator: (cellKey: string, newProps: Object) => void, prevCellKey?: ?string, renderItem: Function, - section: Object, - leadingItem: ?Item, - leadingSection: ?Object, - trailingItem: ?Item, - trailingSection: ?Object, +|}>; + +type ItemWithSeparatorState = { + separatorProps: $ReadOnly<{| + highlighted: false, + ...ItemWithSeparatorCommonProps, + |}>, + leadingSeparatorProps: $ReadOnly<{| + highlighted: false, + ...ItemWithSeparatorCommonProps, + |}>, }; class ItemWithSeparator extends React.Component< ItemWithSeparatorProps, - $FlowFixMeState, + ItemWithSeparatorState, > { state = { separatorProps: { @@ -426,7 +454,7 @@ class ItemWithSeparator extends React.Component< }, updateProps: (select: 'leading' | 'trailing', newProps: Object) => { const {LeadingSeparatorComponent, cellKey, prevCellKey} = this.props; - if (select === 'leading' && LeadingSeparatorComponent) { + if (select === 'leading' && LeadingSeparatorComponent != null) { this.setState(state => ({ leadingSeparatorProps: {...state.leadingSeparatorProps, ...newProps}, })); @@ -439,10 +467,13 @@ class ItemWithSeparator extends React.Component< }, }; - UNSAFE_componentWillReceiveProps(props: ItemWithSeparatorProps) { - this.setState(state => ({ + static getDerivedStateFromProps( + props: ItemWithSeparatorProps, + prevState: ItemWithSeparatorState, + ): ?ItemWithSeparatorState { + return { separatorProps: { - ...this.state.separatorProps, + ...prevState.separatorProps, leadingItem: props.item, leadingSection: props.leadingSection, section: props.section, @@ -450,14 +481,14 @@ class ItemWithSeparator extends React.Component< trailingSection: props.trailingSection, }, leadingSeparatorProps: { - ...this.state.leadingSeparatorProps, + ...prevState.leadingSeparatorProps, leadingItem: props.leadingItem, leadingSection: props.leadingSection, section: props.section, trailingItem: props.item, trailingSection: props.trailingSection, }, - })); + }; } updateSeparatorProps(newProps: Object) { @@ -487,6 +518,9 @@ class ItemWithSeparator extends React.Component< ); return leadingSeparator || separator ? ( + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete + * this comment and run Flow. */ {leadingSeparator} {element} @@ -498,22 +532,29 @@ class ItemWithSeparator extends React.Component< } } -function getItem(sections: ?$ReadOnlyArray, index: number): ?Item { +function getItem( + props: Props>, + sections: ?$ReadOnlyArray, + index: number, +): ?Item { if (!sections) { return null; } let itemIdx = index - 1; - for (let ii = 0; ii < sections.length; ii++) { - if (itemIdx === -1 || itemIdx === sections[ii].data.length) { + for (let i = 0; i < sections.length; i++) { + const section = sections[i]; + const sectionData = section.data; + const itemCount = props.getItemCount(sectionData); + if (itemIdx === -1 || itemIdx === itemCount) { // We intend for there to be overflow by one on both ends of the list. // This will be for headers and footers. When returning a header or footer // item the section itself is the item. - return sections[ii]; - } else if (itemIdx < sections[ii].data.length) { + return section; + } else if (itemIdx < itemCount) { // If we are in the bounds of the list's data then return the item. - return sections[ii].data[itemIdx]; + return props.getItem(sectionData, itemIdx); } else { - itemIdx -= sections[ii].data.length + 2; // Add two for the header and footer + itemIdx -= itemCount + 2; // Add two for the header and footer } } return null; diff --git a/packages/react-native-web/src/vendor/react-native/deepDiffer/index.js b/packages/react-native-web/src/vendor/react-native/deepDiffer/index.js new file mode 100644 index 00000000..4092f889 --- /dev/null +++ b/packages/react-native-web/src/vendor/react-native/deepDiffer/index.js @@ -0,0 +1,72 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow + */ + +'use strict'; + +/* + * @returns {bool} true if different, false if equal + */ +const deepDiffer = function( + one: any, + two: any, + maxDepth: number = -1, +): boolean { + if (maxDepth === 0) { + return true; + } + if (one === two) { + // Short circuit on identical object references instead of traversing them. + return false; + } + if (typeof one === 'function' && typeof two === 'function') { + // We consider all functions equal + return false; + } + if (typeof one !== 'object' || one === null) { + // Primitives can be directly compared + return one !== two; + } + if (typeof two !== 'object' || two === null) { + // We know they are different because the previous case would have triggered + // otherwise. + return true; + } + if (one.constructor !== two.constructor) { + return true; + } + if (Array.isArray(one)) { + // We know two is also an array because the constructors are equal + const len = one.length; + if (two.length !== len) { + return true; + } + for (let ii = 0; ii < len; ii++) { + if (deepDiffer(one[ii], two[ii], maxDepth - 1)) { + return true; + } + } + } else { + for (const key in one) { + if (deepDiffer(one[key], two[key], maxDepth - 1)) { + return true; + } + } + for (const twoKey in two) { + // The only case we haven't checked yet is keys that are in two but aren't + // in one, which means they are different. + if (one[twoKey] === undefined && two[twoKey] !== undefined) { + return true; + } + } + } + return false; +}; + +export default deepDiffer; diff --git a/packages/react-native-web/src/vendor/react-native/emitter/EmitterSubscription.js b/packages/react-native-web/src/vendor/react-native/emitter/EmitterSubscription.js index 63b1aa34..e3b13d02 100644 --- a/packages/react-native-web/src/vendor/react-native/emitter/EmitterSubscription.js +++ b/packages/react-native-web/src/vendor/react-native/emitter/EmitterSubscription.js @@ -1,12 +1,13 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule EmitterSubscription + * @format * @flow */ + 'use strict'; import EventSubscription from './EventSubscription'; diff --git a/packages/react-native-web/src/vendor/react-native/emitter/EventEmitter.js b/packages/react-native-web/src/vendor/react-native/emitter/EventEmitter.js index 87b3782a..51e7e456 100644 --- a/packages/react-native-web/src/vendor/react-native/emitter/EventEmitter.js +++ b/packages/react-native-web/src/vendor/react-native/emitter/EventEmitter.js @@ -1,21 +1,23 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule EventEmitter + * @format * @noflow * @typecheck */ + 'use strict'; import EmitterSubscription from './EmitterSubscription'; import EventSubscriptionVendor from './EventSubscriptionVendor'; -import emptyFunction from 'fbjs/lib/emptyFunction'; import invariant from 'fbjs/lib/invariant'; +const sparseFilterPredicate = () => true; + /** * @class EventEmitter * @description @@ -30,7 +32,6 @@ import invariant from 'fbjs/lib/invariant'; * more advanced emitter may use an EventHolder and EventFactory. */ class EventEmitter { - _subscriber: EventSubscriptionVendor; _currentSubscription: ?EmitterSubscription; @@ -59,12 +60,14 @@ class EventEmitter { * listener */ addListener( - eventType: string, listener: Function, context: ?Object): EmitterSubscription { - + eventType: string, + listener: Function, + context: ?Object, + ): EmitterSubscription { return (this._subscriber.addSubscription( eventType, - new EmitterSubscription(this, this._subscriber, listener, context) - ) : any); + new EmitterSubscription(this, this._subscriber, listener, context), + ): any); } /** @@ -77,7 +80,11 @@ class EventEmitter { * @param {*} context - Optional context object to use when invoking the * listener */ - once(eventType: string, listener: Function, context: ?Object): EmitterSubscription { + once( + eventType: string, + listener: Function, + context: ?Object, + ): EmitterSubscription { return this.addListener(eventType, (...args) => { this.removeCurrentListener(); listener.apply(context, args); @@ -119,7 +126,7 @@ class EventEmitter { removeCurrentListener() { invariant( !!this._currentSubscription, - 'Not in an emitting cycle; there is no current subscription' + 'Not in an emitting cycle; there is no current subscription', ); this.removeSubscription(this._currentSubscription); } @@ -131,7 +138,7 @@ class EventEmitter { removeSubscription(subscription: EmitterSubscription) { invariant( subscription.emitter === this, - 'Subscription does not belong to this emitter.' + 'Subscription does not belong to this emitter.', ); this._subscriber.removeSubscription(subscription); } @@ -144,12 +151,15 @@ class EventEmitter { * @returns {array} */ listeners(eventType: string): [EmitterSubscription] { - const subscriptions: ?[EmitterSubscription] = (this._subscriber.getSubscriptionsForType(eventType): any); + const subscriptions = this._subscriber.getSubscriptionsForType(eventType); return subscriptions - ? subscriptions.filter(emptyFunction.thatReturnsTrue).map( - function(subscription) { - return subscription.listener; - }) + ? subscriptions + // We filter out missing entries because the array is sparse. + // "callbackfn is called only for elements of the array which actually + // exist; it is not called for missing elements of the array." + // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-array.prototype.filter + .filter(sparseFilterPredicate) + .map(subscription => subscription.listener) : []; } @@ -168,17 +178,17 @@ class EventEmitter { * emitter.emit('someEvent', 'abc'); // logs 'abc' */ emit(eventType: string) { - const subscriptions: ?[EmitterSubscription] = (this._subscriber.getSubscriptionsForType(eventType): any); + const subscriptions = this._subscriber.getSubscriptionsForType(eventType); if (subscriptions) { for (let i = 0, l = subscriptions.length; i < l; i++) { const subscription = subscriptions[i]; // The subscription may have been removed during this event loop. - if (subscription) { + if (subscription && subscription.listener) { this._currentSubscription = subscription; subscription.listener.apply( subscription.context, - Array.prototype.slice.call(arguments, 1) + Array.prototype.slice.call(arguments, 1), ); } } @@ -200,7 +210,7 @@ class EventEmitter { * */ removeListener(eventType: String, listener) { - const subscriptions: ?[EmitterSubscription] = (this._subscriber.getSubscriptionsForType(eventType): any); + const subscriptions = this._subscriber.getSubscriptionsForType(eventType); if (subscriptions) { for (let i = 0, l = subscriptions.length; i < l; i++) { const subscription = subscriptions[i]; diff --git a/packages/react-native-web/src/vendor/react-native/emitter/EventEmitterWithHolding.js b/packages/react-native-web/src/vendor/react-native/emitter/EventEmitterWithHolding.js index 45ba0df9..49247981 100644 --- a/packages/react-native-web/src/vendor/react-native/emitter/EventEmitterWithHolding.js +++ b/packages/react-native-web/src/vendor/react-native/emitter/EventEmitterWithHolding.js @@ -1,12 +1,13 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule EventEmitterWithHolding + * @format * @flow */ + 'use strict'; import type EmitterSubscription from './EmitterSubscription'; @@ -26,7 +27,6 @@ import type EventHolder from './EventHolder'; * that uses an emitter. */ class EventEmitterWithHolding { - _emitter: EventEmitter; _eventHolder: EventHolder; _currentEventToken: ?Object; @@ -81,8 +81,15 @@ class EventEmitterWithHolding { * }); // logs 'abc' */ addRetroactiveListener( - eventType: string, listener: Function, context: ?Object): EmitterSubscription { - const subscription = this._emitter.addListener(eventType, listener, context); + eventType: string, + listener: Function, + context: ?Object, + ): EmitterSubscription { + const subscription = this._emitter.addListener( + eventType, + listener, + context, + ); this._emittingHeldEvents = true; this._eventHolder.emitToListener(eventType, listener, context); diff --git a/packages/react-native-web/src/vendor/react-native/emitter/EventHolder.js b/packages/react-native-web/src/vendor/react-native/emitter/EventHolder.js index 4c4c1d8d..044f49b6 100644 --- a/packages/react-native-web/src/vendor/react-native/emitter/EventHolder.js +++ b/packages/react-native-web/src/vendor/react-native/emitter/EventHolder.js @@ -1,18 +1,18 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule EventHolder + * @format * @flow */ + 'use strict'; import invariant from 'fbjs/lib/invariant'; class EventHolder { - _heldEvents: Object; _currentEventKey: ?Object; @@ -47,7 +47,7 @@ class EventHolder { const eventsOfType = this._heldEvents[eventType]; const key = { eventType: eventType, - index: eventsOfType.length + index: eventsOfType.length, }; eventsOfType.push(args); return key; @@ -61,7 +61,7 @@ class EventHolder { * @param {?object} context - Optional context object to use when invoking * the listener */ - emitToListener(eventType: ?string , listener: Function, context: ?Object) { + emitToListener(eventType: ?string, listener: Function, context: ?Object) { const eventsOfType = this._heldEvents[eventType]; if (!eventsOfType) { return; @@ -73,7 +73,7 @@ class EventHolder { } this._currentEventKey = { eventType: eventType, - index: index + index: index, }; listener.apply(context, eventHeld); }); @@ -91,7 +91,7 @@ class EventHolder { releaseCurrentEvent() { invariant( this._currentEventKey !== null, - 'Not in an emitting cycle; there is no current event' + 'Not in an emitting cycle; there is no current event', ); this._currentEventKey && this.releaseEvent(this._currentEventKey); } diff --git a/packages/react-native-web/src/vendor/react-native/emitter/EventSubscription.js b/packages/react-native-web/src/vendor/react-native/emitter/EventSubscription.js index fcb6896b..ca0a8c91 100644 --- a/packages/react-native-web/src/vendor/react-native/emitter/EventSubscription.js +++ b/packages/react-native-web/src/vendor/react-native/emitter/EventSubscription.js @@ -1,12 +1,13 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule EventSubscription - * @flow + * @format + * @flow strict-local */ + 'use strict'; import type EventSubscriptionVendor from './EventSubscriptionVendor'; @@ -16,7 +17,6 @@ import type EventSubscriptionVendor from './EventSubscriptionVendor'; * remove its own subscription. */ class EventSubscription { - eventType: string; key: number; subscriber: EventSubscriptionVendor; diff --git a/packages/react-native-web/src/vendor/react-native/emitter/EventSubscriptionVendor.js b/packages/react-native-web/src/vendor/react-native/emitter/EventSubscriptionVendor.js index b01bd150..22a0817f 100644 --- a/packages/react-native-web/src/vendor/react-native/emitter/EventSubscriptionVendor.js +++ b/packages/react-native-web/src/vendor/react-native/emitter/EventSubscriptionVendor.js @@ -1,12 +1,13 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule EventSubscriptionVendor + * @format * @flow */ + 'use strict'; import invariant from 'fbjs/lib/invariant'; @@ -18,7 +19,6 @@ import type EventSubscription from './EventSubscription'; * subscribed to a particular event type. */ class EventSubscriptionVendor { - _subscriptionsForType: Object; _currentSubscription: ?EventSubscription; @@ -34,10 +34,13 @@ class EventSubscriptionVendor { * @param {EventSubscription} subscription */ addSubscription( - eventType: string, subscription: EventSubscription): EventSubscription { + eventType: string, + subscription: EventSubscription, + ): EventSubscription { invariant( subscription.subscriber === this, - 'The subscriber of the subscription is incorrectly set.'); + 'The subscriber of the subscription is incorrectly set.', + ); if (!this._subscriptionsForType[eventType]) { this._subscriptionsForType[eventType] = []; } @@ -91,7 +94,7 @@ class EventSubscriptionVendor { * @returns {?array} */ getSubscriptionsForType(eventType: string): ?[EventSubscription] { - return this._subscriptionsForType[eventType]; + return this._subscriptionsForType[eventType]; } } diff --git a/packages/react-native-web/src/vendor/react-native/emitter/EventValidator.js b/packages/react-native-web/src/vendor/react-native/emitter/EventValidator.js index 485bfb61..4e389a75 100644 --- a/packages/react-native-web/src/vendor/react-native/emitter/EventValidator.js +++ b/packages/react-native-web/src/vendor/react-native/emitter/EventValidator.js @@ -1,12 +1,13 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule EventValidator + * @format * @flow */ + 'use strict'; const __DEV__ = process.env.NODE_ENV !== 'production'; @@ -39,11 +40,11 @@ const EventValidator = { emit: function emit(type, a, b, c, d, e, _) { assertAllowsEventType(type, eventTypes); return emitter.emit.call(this, type, a, b, c, d, e, _); - } + }, }); return emitterWithValidation; - } + }, }; function assertAllowsEventType(type, allowedTypes) { @@ -63,7 +64,7 @@ function errorMessageFor(type, allowedTypes) { // Allow for good error messages if (__DEV__) { - var recommendationFor = function (type, allowedTypes) { + var recommendationFor = function(type, allowedTypes) { const closestTypeRecommendation = closestTypeFor(type, allowedTypes); if (isCloseEnough(closestTypeRecommendation, type)) { return 'Did you mean "' + closestTypeRecommendation.type + '"? '; @@ -72,21 +73,21 @@ if (__DEV__) { } }; - var closestTypeFor = function (type, allowedTypes) { + const closestTypeFor = function(type, allowedTypes) { const typeRecommendations = allowedTypes.map( - typeRecommendationFor.bind(this, type) + typeRecommendationFor.bind(this, type), ); return typeRecommendations.sort(recommendationSort)[0]; }; - var typeRecommendationFor = function (type, recommendedType) { + const typeRecommendationFor = function(type, recommendedType) { return { type: recommendedType, - distance: damerauLevenshteinDistance(type, recommendedType) + distance: damerauLevenshteinDistance(type, recommendedType), }; }; - var recommendationSort = function (recommendationA, recommendationB) { + const recommendationSort = function(recommendationA, recommendationB) { if (recommendationA.distance < recommendationB.distance) { return -1; } else if (recommendationA.distance > recommendationB.distance) { @@ -96,11 +97,11 @@ if (__DEV__) { } }; - var isCloseEnough = function (closestType, actualType) { - return (closestType.distance / actualType.length) < 0.334; + const isCloseEnough = function(closestType, actualType) { + return closestType.distance / actualType.length < 0.334; }; - var damerauLevenshteinDistance = function (a, b) { + const damerauLevenshteinDistance = function(a, b) { let i, j; const d = []; @@ -119,12 +120,15 @@ if (__DEV__) { d[i][j] = Math.min( d[i - 1][j] + 1, d[i][j - 1] + 1, - d[i - 1][j - 1] + cost + d[i - 1][j - 1] + cost, ); - if (i > 1 && j > 1 && - a.charAt(i - 1) === b.charAt(j - 2) && - a.charAt(i - 2) === b.charAt(j - 1)) { + if ( + i > 1 && + j > 1 && + a.charAt(i - 1) === b.charAt(j - 2) && + a.charAt(i - 2) === b.charAt(j - 1) + ) { d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + cost); } } diff --git a/packages/react-native-web/src/vendor/react-native/emitter/mixInEventEmitter.js b/packages/react-native-web/src/vendor/react-native/emitter/mixInEventEmitter.js index c869880f..65278e2d 100644 --- a/packages/react-native-web/src/vendor/react-native/emitter/mixInEventEmitter.js +++ b/packages/react-native-web/src/vendor/react-native/emitter/mixInEventEmitter.js @@ -1,23 +1,20 @@ /** - * Copyright (c) 2015-present, Facebook, Inc. + * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @providesModule mixInEventEmitter + * @format * @flow */ + 'use strict'; import EventEmitter from './EventEmitter'; import EventEmitterWithHolding from './EventEmitterWithHolding'; import EventHolder from './EventHolder'; -import EventValidator from './EventValidator'; import invariant from 'fbjs/lib/invariant'; -/* $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. */ import keyOf from 'fbjs/lib/keyOf'; import type EmitterSubscription from './EmitterSubscription'; @@ -59,7 +56,7 @@ function mixInEventEmitter(cls: Function | Object, types: Object) { if (ctor) { invariant( ctor === Object || ctor === Function, - 'Mix EventEmitter into a class, not an instance' + 'Mix EventEmitter into a class, not an instance', ); } @@ -96,7 +93,7 @@ const EventEmitterMixin = { return this.__getEventEmitter().addRetroactiveListener( eventType, listener, - context + context, ); }, @@ -124,6 +121,7 @@ const EventEmitterMixin = { if (!this.__eventEmitter) { let emitter = new EventEmitter(); if (__DEV__) { + const EventValidator = require('./EventValidator').default; emitter = EventValidator.addValidation(emitter, this.__types); } @@ -131,7 +129,7 @@ const EventEmitterMixin = { this.__eventEmitter = new EventEmitterWithHolding(emitter, holder); } return this.__eventEmitter; - } + }, }; export default mixInEventEmitter; diff --git a/packages/react-native-web/src/vendor/react-native/isEmpty/index.js b/packages/react-native-web/src/vendor/react-native/isEmpty/index.js deleted file mode 100644 index 31750758..00000000 --- a/packages/react-native-web/src/vendor/react-native/isEmpty/index.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @providesModule isEmpty - */ -'use strict'; - -/** - * Mimics empty from PHP. - */ -function isEmpty(obj) { - if (Array.isArray(obj)) { - return obj.length === 0; - } else if (typeof obj === 'object') { - for (var i in obj) { - return false; - } - return true; - } else { - return !obj; - } -} - -export default isEmpty; diff --git a/scripts/babel/preset.js b/scripts/babel/preset.js index 57942333..2769bbdf 100644 --- a/scripts/babel/preset.js +++ b/scripts/babel/preset.js @@ -31,7 +31,8 @@ const createConfig = ({ modules }) => ({ '@babel/plugin-transform-flow-strip-types', ['babel-plugin-transform-react-remove-prop-types', { mode: 'wrap' }], ['@babel/plugin-proposal-class-properties', { loose: true }], - ['@babel/plugin-proposal-object-rest-spread', { useBuiltIns: true }] + ['@babel/plugin-proposal-object-rest-spread', { useBuiltIns: true }], + '@babel/plugin-proposal-nullish-coalescing-operator' ].concat(modules ? ['babel-plugin-add-module-exports'] : []) }); diff --git a/yarn.lock b/yarn.lock index b301317f..34e027d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -262,6 +262,14 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.2.0" +"@babel/plugin-proposal-nullish-coalescing-operator@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.4.4.tgz#41c360d59481d88e0ce3a3f837df10121a769b39" + integrity sha512-Amph7Epui1Dh/xxUxS2+K22/MUi6+6JVTvy3P58tja3B6yKTSjwwx0/d83rF7551D6PVSSoplQb8GCwqec7HRw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.2.0" + "@babel/plugin-proposal-object-rest-spread@^7.0.0", "@babel/plugin-proposal-object-rest-spread@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.2.0.tgz#88f5fec3e7ad019014c97f7ee3c992f0adbf7fb8"