mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-01-12 22:51:09 +08:00
[change] Update vendor code
* FlatList * SectionList * VirtualizedList * VirtualizedSectionList
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -45,7 +45,6 @@ module.exports = {
|
||||
Share: true,
|
||||
StatusBar: true,
|
||||
StyleSheet: true,
|
||||
SwipeableFlatList: true,
|
||||
Switch: true,
|
||||
Systrace: true,
|
||||
TVEventHandler: true,
|
||||
|
||||
@@ -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;
|
||||
2
packages/react-native-web/src/index.js
vendored
2
packages/react-native-web/src/index.js
vendored
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import performanceNow from 'fbjs/lib/performanceNow';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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(
|
||||
'`' +
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
137
packages/react-native-web/src/vendor/react-native/Types/CoreEventTypes.js
vendored
Normal file
137
packages/react-native-web/src/vendor/react-native/Types/CoreEventTypes.js
vendored
Normal 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,
|
||||
|}>,
|
||||
>;
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
72
packages/react-native-web/src/vendor/react-native/deepDiffer/index.js
vendored
Normal file
72
packages/react-native-web/src/vendor/react-native/deepDiffer/index.js
vendored
Normal 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;
|
||||
@@ -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';
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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'] : [])
|
||||
});
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user