[change] Update vendor code

* FlatList
* SectionList
* VirtualizedList
* VirtualizedSectionList
This commit is contained in:
Nicolas Gallagher
2019-07-01 16:24:14 -07:00
parent c4a2a6d4a3
commit 25dd43e960
36 changed files with 1305 additions and 1346 deletions

View File

@@ -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",

View File

@@ -45,7 +45,6 @@ module.exports = {
Share: true,
StatusBar: true,
StyleSheet: true,
SwipeableFlatList: true,
Switch: true,
Systrace: true,
TVEventHandler: true,

View File

@@ -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;

View File

@@ -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,

View File

@@ -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';
/**

View File

@@ -8,6 +8,8 @@
* @format
*/
'use strict';
import performanceNow from 'fbjs/lib/performanceNow';
import warning from 'fbjs/lib/warning';

View File

@@ -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<ViewProps, 'style'>;
type RequiredProps<ItemT> = {
/**
* Takes an item from `data` and renders it into the list. Example usage:
@@ -56,7 +65,7 @@ type RequiredProps<ItemT> = {
item: ItemT,
index: number,
separators: SeparatorsObj,
}) => ?React.Element<any>,
}) => ?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<ItemT> = {
* a rendered element.
*/
ListFooterComponent?: ?(React.ComponentType<any> | React.Element<any>),
/**
* 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<any> | React.Element<any>),
/**
* 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<ItemT> = {
* @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<ItemT> = RequiredProps<ItemT> &
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<ItemT> extends React.PureComponent<Props<ItemT>, 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<ItemT>) {
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<ItemT>) {
super(props);
this._checkProps(this.props);
if (this.props.viewabilityConfigCallbackPairs) {
this._virtualizedListPairs = this.props.viewabilityConfigCallbackPairs.map(
pair => ({
@@ -465,8 +465,30 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
}
}
_hasWarnedLegacy = false;
_listRef: null | VirtualizedList;
componentDidUpdate(prevProps: Props<ItemT>) {
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<typeof VirtualizedList>;
_virtualizedListPairs: Array<ViewabilityConfigCallbackPair> = [];
_captureRef = ref => {
@@ -478,7 +500,6 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
getItem,
getItemCount,
horizontal,
legacyImplementation,
numColumns,
columnWrapperStyle,
onViewableItemsChanged,
@@ -496,21 +517,6 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, 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<ItemT> extends React.PureComponent<Props<ItemT>, 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<ItemT> extends React.PureComponent<Props<ItemT>, 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<ItemT> extends React.PureComponent<Props<ItemT>, void> {
'Expected array of items with numColumns > 1',
);
return (
<View style={[{flexDirection: 'row'}, columnWrapperStyle]}>
<View
style={StyleSheet.compose(
styles.row,
columnWrapperStyle,
)}>
{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 ? (
<React.Fragment key={kk}>{element}</React.Fragment>
) : null;
})}
</View>
);
@@ -618,34 +632,22 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, 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. */
<UnimplementedView
{...this.props}
/* $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={this.props.data}
ref={this._captureRef}
/>
);
} else {
return (
<VirtualizedList
{...this.props}
renderItem={this._renderItem}
getItem={this._getItem}
getItemCount={this._getItemCount}
keyExtractor={this._keyExtractor}
ref={this._captureRef}
viewabilityConfigCallbackPairs={this._virtualizedListPairs}
/>
);
}
return (
<VirtualizedList
{...this.props}
renderItem={this._renderItem}
getItem={this._getItem}
getItemCount={this._getItemCount}
keyExtractor={this._keyExtractor}
ref={this._captureRef}
viewabilityConfigCallbackPairs={this._virtualizedListPairs}
/>
);
}
}
const styles = StyleSheet.create({
row: {flexDirection: 'row'},
});
export default FlatList;

View File

@@ -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;

View File

@@ -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<typeof Types>,
property?: $Enum<typeof Properties>,
};
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<mixed>) {
console.error('LayoutAnimation.checkConfig(...) has been disabled.');
},
Presets,
easeInEaseOut: configureNext.bind(null, Presets.easeInEaseOut),
linear: configureNext.bind(null, Presets.linear),

View File

@@ -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(
'`' +

View File

@@ -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';

View File

@@ -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<PressEvent, 'touchHistory'>,
) {
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<typeof PanResponder, 'create'>,
PanResponderConfig,
>;
export default PanResponder;

View File

@@ -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

View File

@@ -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<SectionItemT> = {
/**
* The data for rendering items in this section.
*/
data: $ReadOnlyArray<SectionItemT>,
/**
* 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<SectionItemT>,
separators: {
highlight: () => void,
unhighlight: () => void,
updateProps: (select: 'leading' | 'trailing', newProps: Object) => void,
},
}) => ?React.Element<any>,
ItemSeparatorComponent?: ?React.ComponentType<any>,
keyExtractor?: (item: SectionItemT) => string,
// TODO: support more optional/override props
// onViewableItemsChanged?: ...
};
export type SectionBase<SectionItemT> = _SectionBase<SectionItemT>;
type RequiredProps<SectionT: SectionBase<any>> = {
/**
@@ -179,7 +156,10 @@ type OptionalProps<SectionT: SectionBase<any>> = {
*/
stickySectionHeadersEnabled?: boolean,
legacyImplementation?: ?boolean,
/**
* The legacy implementation is no longer supported.
*/
legacyImplementation?: empty,
};
export type Props<SectionT> = RequiredProps<SectionT> &
@@ -272,7 +252,9 @@ class SectionList<SectionT: SectionBase<any>> 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<SectionT: SectionBase<any>> 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 <List {...this.props} ref={this._captureRef} />;
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. */
<VirtualizedSectionList
{...this.props}
ref={this._captureRef}
getItemCount={items => items.length}
getItem={(items, index) => items[index]}
/>
);
}
_wrapperListRef: VirtualizedSectionList<any>;
_wrapperListRef: ?React.ElementRef<typeof VirtualizedSectionList>;
_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;
};
}

View File

@@ -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<Props> {
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<Props> {
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;

View File

@@ -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 (
* <StaticRenderer render={() => <MyComponent value={someValue} />} />
* );
*
* 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<Props> {
static propTypes = {
render: func.isRequired,
shouldUpdate: bool.isRequired
};
shouldComponentUpdate(nextProps: { shouldUpdate: boolean }): boolean {
class StaticRenderer extends React.Component<Props> {
shouldComponentUpdate(nextProps: Props): boolean {
return nextProps.shouldUpdate;
}
render() {
render(): React.Node {
return this.props.render();
}
}
export default StaticRenderer;

View File

@@ -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<ItemT> = SwipableListProps & FlatListProps<ItemT>;
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.
*
* <SwipeableListView renderRow={..} renderQuickActions={..} {..FlatList props} />
*
* 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<ItemT> extends React.Component<Props<ItemT>, State> {
props: Props<ItemT>;
state: State;
_flatListRef: ?FlatList<ItemT> = 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<ItemT>, context: any): void {
super(props, context);
this.state = {
openRowKey: null,
};
this._shouldBounceFirstRowOnMount = this.props.bounceFirstRowOnMount;
}
render(): React.Node {
return (
<FlatList
{...this.props}
ref={ref => {
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<any> => {
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 (
<SwipeableRow
slideoutView={slideoutView}
isOpen={key === this.state.openRowKey}
maxSwipeDistance={this._getMaxSwipeDistance(info)}
onOpen={() => this._onOpen(key)}
onClose={() => this._onClose(key)}
shouldBounceOnMount={shouldBounceOnMount}
onSwipeEnd={this._setListViewScrollable}
onSwipeStart={this._setListViewNotScrollable}>
{this.props.renderItem(info)}
</SwipeableRow>
);
};
// 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;

View File

@@ -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<any> {
// The view hidden behind the main view
let slideOutView;
if (this.state.isSwipeableViewRendered && this.state.rowHeight) {
slideOutView = (
<View style={[
styles.slideOutContainer,
{height: this.state.rowHeight},
]}>
{this.props.slideoutView}
</View>
);
}
// The swipeable item
const swipeableView = (
<Animated.View
onLayout={this._onSwipeableViewLayout}
style={{transform: [{translateX: this.state.currentLeft}]}}>
{this.props.children}
</Animated.View>
);
return (
<View
{...this._panResponder.panHandlers}>
{slideOutView}
{swipeableView}
</View>
);
},
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;

View File

@@ -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;

View File

@@ -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<T> = $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<T> = $ReadOnly<{|
...SyntheticEvent<T>,
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<TextLayout>,
|}>,
>;
export type PressEvent = ResponderSyntheticEvent<
$ReadOnly<{|
changedTouches: $ReadOnlyArray<$PropertyType<PressEvent, 'nativeEvent'>>,
force: number,
identifier: number,
locationX: number,
locationY: number,
pageX: number,
pageY: number,
target: ?number,
timestamp: number,
touches: $ReadOnlyArray<$PropertyType<PressEvent, 'nativeEvent'>>,
|}>,
>;
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,
|}>,
>;

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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<ViewProps, 'style'>;
export type renderItemType = (info: any) => ?React.Element<any>;
const __DEV__ = process.env.NODE_ENV !== 'production';
type ViewabilityHelperCallbackTuple = {
viewabilityHelper: ViewabilityHelper,
onViewableItemsChanged: (info: {
@@ -49,7 +54,7 @@ type RequiredProps = {
// `VirtualizedSectionList`'s props.
renderItem: $FlowFixMe<renderItemType>,
/**
* 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<any> | React.Element<any>),
/**
* 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<any> | React.Element<any>),
/**
* 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<ViewToken>,
changed: Array<ViewToken>,
}) => 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
* <RefreshControl> component built internally. The onRefresh and refreshing
* props are also ignored. Only works for vertical VirtualizedList.
*/
refreshControl?: ?React.Element<any>,
/**
* 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<Props, State> {
* 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<Props, State> {
* 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<Props, State> {
* 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<Props, State> {
}
}
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<Props, State> {
}
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<Props, State> {
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<Props, State> {
}
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<Props, State> {
stickyIndicesFromProps: Set<number>,
first: number,
last: number,
inversionStyle: ?DangerouslyImpreciseStyleProp,
inversionStyle: ViewStyleProp,
) {
const {
CellRendererComponent,
@@ -692,7 +719,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
};
_isVirtualizationDisabled(): boolean {
return this.props.disableVirtualization;
return this.props.disableVirtualization || false;
}
_isNestedWithSameOrientation(): boolean {
@@ -740,12 +767,16 @@ class VirtualizedList extends React.PureComponent<Props, State> {
<VirtualizedCellWrapper
cellKey={this._getCellKey() + '-header'}
key="$header">
<View onLayout={this._onLayoutHeader} style={inversionStyle}>
{/*
Flow doesn't know this is a React.Element and not a React.Component
$FlowFixMe https://fburl.com/b9xmtm09
*/}
{element}
<View
onLayout={this._onLayoutHeader}
style={StyleSheet.compose(
inversionStyle,
this.props.ListHeaderComponentStyle,
)}>
{
// $FlowFixMe - Typing ReactNativeComponent revealed errors
element
}
</View>
</VirtualizedCellWrapper>,
);
@@ -753,6 +784,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
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<Props, State> {
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(
<View key="$sticky_lead" style={{[spacerKey]: leadSpace}} />,
);
@@ -820,8 +854,9 @@ class VirtualizedList extends React.PureComponent<Props, State> {
);
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<Props, State> {
);
}
} else if (ListEmptyComponent) {
const element = React.isValidElement(ListEmptyComponent) ? (
const element: React.Element<any> = ((React.isValidElement(
ListEmptyComponent,
) ? (
ListEmptyComponent
) : (
// $FlowFixMe
<ListEmptyComponent />
);
)): any);
cells.push(
<View
key="$empty"
onLayout={this._onLayoutEmpty}
style={inversionStyle}>
{/*
Flow doesn't know this is a React.Element and not a React.Component
$FlowFixMe https://fburl.com/b9xmtm09
*/}
{element}
</View>,
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<Props, State> {
<VirtualizedCellWrapper
cellKey={this._getCellKey() + '-footer'}
key="$footer">
<View onLayout={this._onLayoutFooter} style={inversionStyle}>
{/*
Flow doesn't know this is a React.Element and not a React.Component
$FlowFixMe https://fburl.com/b9xmtm09
*/}
{element}
<View
onLayout={this._onLayoutFooter}
style={StyleSheet.compose(
inversionStyle,
this.props.ListFooterComponentStyle,
)}>
{
// $FlowFixMe - Typing ReactNativeComponent revealed errors
element
}
</View>
</VirtualizedCellWrapper>,
);
@@ -892,12 +936,16 @@ class VirtualizedList extends React.PureComponent<Props, State> {
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<Props, State> {
);
if (this.props.debug) {
return (
<View style={{flex: 1}}>
<View style={styles.debug}>
{ret}
{this._renderDebugOverlay()}
</View>
@@ -936,7 +984,19 @@ class VirtualizedList extends React.PureComponent<Props, State> {
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<Props, State> {
_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<number, string> = new Map();
_hasDoneInitialScroll = false;
_nestedChildLists: Map<
string,
{ref: ?VirtualizedList, state: ?ChildListState},
@@ -989,9 +1050,11 @@ class VirtualizedList extends React.PureComponent<Props, State> {
}
_defaultRenderScrollComponent = props => {
const onRefresh = props.onRefresh;
if (this._isNestedWithSameOrientation()) {
// $FlowFixMe - Typing ReactNativeComponent revealed errors
return <View {...props} />;
} 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<Props, State> {
'`',
);
return (
// $FlowFixMe Invalid prop usage
<ScrollView
{...props}
refreshControl={
/* $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. */
<RefreshControl
refreshing={props.refreshing}
onRefresh={props.onRefresh}
progressViewOffset={props.progressViewOffset}
/>
props.refreshControl == null ? (
<RefreshControl
refreshing={props.refreshing}
onRefresh={onRefresh}
progressViewOffset={props.progressViewOffset}
/>
) : (
props.refreshControl
)
}
/>
);
} else {
// $FlowFixMe Invalid prop usage
return <ScrollView {...props} />;
}
};
@@ -1046,7 +1112,19 @@ class VirtualizedList extends React.PureComponent<Props, State> {
} 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<Props, State> {
}
};
_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<Props, State> {
_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<Props, State> {
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 (
<View
style={{
...baseStyle,
bottom: 0,
width: 20,
borderColor: 'blue',
borderWidth: 1,
}}>
<View style={[styles.debugOverlayBase, styles.debugOverlay]}>
{framesInLayout.map((f, ii) => (
<View
key={'f' + ii}
style={{
...baseStyle,
left: 0,
top: f.offset * normalize,
height: f.length * normalize,
backgroundColor: 'orange',
}}
style={[
styles.debugOverlayBase,
styles.debugOverlayFrame,
{
top: f.offset * normalize,
height: f.length * normalize,
},
]}
/>
))}
<View
style={{
...baseStyle,
left: 0,
top: windowTop * normalize,
height: windowLen * normalize,
borderColor: 'green',
borderWidth: 2,
}}
style={[
styles.debugOverlayBase,
styles.debugOverlayFrameLast,
{
top: windowTop * normalize,
height: windowLen * normalize,
},
]}
/>
<View
style={{
...baseStyle,
left: 0,
top: visTop * normalize,
height: visLen * normalize,
borderColor: 'red',
borderWidth: 2,
}}
style={[
styles.debugOverlayBase,
styles.debugOverlayFrameVis,
{
top: visTop * normalize,
height: visLen * normalize,
},
]}
/>
</View>
);
@@ -1323,18 +1415,26 @@ class VirtualizedList extends React.PureComponent<Props, State> {
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<Props, State> {
// 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. */
<View style={cellStyle} onLayout={onLayout}>
{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;

View File

@@ -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<SectionItem>,
export type SectionBase<SectionItemT> = {
/**
* The data for rendering items in this section.
*/
data: $ReadOnlyArray<SectionItemT>,
/**
* 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<SectionItemT>,
separators: {
highlight: () => void,
unhighlight: () => void,
updateProps: (select: 'leading' | 'trailing', newProps: Object) => void,
},
}) => ?React.Element<any>,
ItemSeparatorComponent?: ?React.ComponentType<*>,
keyExtractor?: (item: SectionItem, index: ?number) => string,
// TODO: support more optional/override props
// FooterComponent?: ?ReactClass<*>,
// HeaderComponent?: ?ReactClass<*>,
// onViewableItemsChanged?: ({viewableItems: Array<ViewToken>, changed: Array<ViewToken>}) => void,
ItemSeparatorComponent?: ?React.ComponentType<any>,
keyExtractor?: (item: SectionItemT, index?: ?number) => string,
};
type RequiredProps<SectionT: SectionBase> = {
type RequiredProps<SectionT: SectionBase<any>> = {
sections: $ReadOnlyArray<SectionT>,
};
type OptionalProps<SectionT: SectionBase> = {
type OptionalProps<SectionT: SectionBase<any>> = {
/**
* Rendered after the last item in the last section.
*/
ListFooterComponent?: ?(React.ComponentType<*> | React.Element<any>),
ListFooterComponent?: ?(React.ComponentType<any> | React.Element<any>),
/**
* Rendered at the very beginning of the list.
*/
ListHeaderComponent?: ?(React.ComponentType<*> | React.Element<any>),
ListHeaderComponent?: ?(React.ComponentType<any> | React.Element<any>),
/**
* Default renderer for every item in every section.
*/
@@ -80,24 +84,24 @@ type OptionalProps<SectionT: SectionBase> = {
* Rendered at the bottom of every Section, except the very last one, in place of the normal
* ItemSeparatorComponent.
*/
SectionSeparatorComponent?: ?React.ComponentType<*>,
SectionSeparatorComponent?: ?React.ComponentType<any>,
/**
* Rendered at the bottom of every Item except the very last one in the last section.
*/
ItemSeparatorComponent?: ?React.ComponentType<*>,
ItemSeparatorComponent?: ?React.ComponentType<any>,
/**
* 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<SectionT: SectionBase> extends React.PureComponent<
Props<SectionT>,
State,
> {
props: Props<SectionT>;
state: State;
class VirtualizedSectionList<
SectionT: SectionBase<any>,
> extends React.PureComponent<Props<SectionT>, State> {
static defaultProps: DefaultProps = {
...VirtualizedList.defaultProps,
data: [],
@@ -145,12 +144,20 @@ class VirtualizedSectionList<SectionT: SectionBase> 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<SectionT: SectionBase> extends React.PureComponent<
return this._listRef;
}
constructor(props: Props<SectionT>, context: Object) {
super(props, context);
this.state = this._computeState(props);
}
UNSAFE_componentWillReceiveProps(nextProps: Props<SectionT>) {
this.setState(this._computeState(nextProps));
}
_computeState(props: Props<SectionT>): 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 (
<VirtualizedList {...this.state.childProps} ref={this._captureRef} />
);
}
_keyExtractor = (item: Item, index: number) => {
const info = this._subExtractor(index);
return (info && info.key) || String(index);
@@ -178,39 +230,41 @@ class VirtualizedSectionList<SectionT: SectionBase> 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<SectionT: SectionBase> extends React.PureComponent<
_getSeparatorComponent(
index: number,
info?: ?Object,
): ?React.ComponentType<*> {
): ?React.ComponentType<any> {
info = info || this._subExtractor(index);
if (!info) {
return null;
@@ -312,7 +366,8 @@ class VirtualizedSectionList<SectionT: SectionBase> 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<SectionT: SectionBase> extends React.PureComponent<
return null;
}
_computeState(props: Props<SectionT>): 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<SectionT>, context: Object) {
super(props, context);
this.state = this._computeState(props);
}
UNSAFE_componentWillReceiveProps(nextProps: Props<SectionT>) {
this.setState(this._computeState(nextProps));
}
render() {
return (
<VirtualizedList {...this.state.childProps} ref={this._captureRef} />
);
}
_cellRefs = {};
_listRef: VirtualizedList;
_captureRef = ref => {
@@ -374,25 +387,40 @@ class VirtualizedSectionList<SectionT: SectionBase> 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<any>,
SeparatorComponent: ?React.ComponentType<any>,
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<
<SeparatorComponent {...this.state.separatorProps} />
);
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. */
<View>
{leadingSeparator}
{element}
@@ -498,22 +532,29 @@ class ItemWithSeparator extends React.Component<
}
}
function getItem(sections: ?$ReadOnlyArray<Item>, index: number): ?Item {
function getItem(
props: Props<SectionBase<any>>,
sections: ?$ReadOnlyArray<Item>,
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;

View File

@@ -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;

View File

@@ -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';

View File

@@ -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];

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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];
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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'] : [])
});

View File

@@ -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"