yeet
This commit is contained in:
245
node_modules/react-native/Libraries/Lists/FillRateHelper.js
generated
vendored
Normal file
245
node_modules/react-native/Libraries/Lists/FillRateHelper.js
generated
vendored
Normal file
@ -0,0 +1,245 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
const performanceNow = require('fbjs/lib/performanceNow');
|
||||
const warning = require('fbjs/lib/warning');
|
||||
|
||||
export type FillRateInfo = Info;
|
||||
|
||||
class Info {
|
||||
any_blank_count: number = 0;
|
||||
any_blank_ms: number = 0;
|
||||
any_blank_speed_sum: number = 0;
|
||||
mostly_blank_count: number = 0;
|
||||
mostly_blank_ms: number = 0;
|
||||
pixels_blank: number = 0;
|
||||
pixels_sampled: number = 0;
|
||||
pixels_scrolled: number = 0;
|
||||
total_time_spent: number = 0;
|
||||
sample_count: number = 0;
|
||||
}
|
||||
|
||||
type FrameMetrics = {
|
||||
inLayout?: boolean,
|
||||
length: number,
|
||||
offset: number,
|
||||
...
|
||||
};
|
||||
|
||||
const DEBUG = false;
|
||||
|
||||
let _listeners: Array<(Info) => void> = [];
|
||||
let _minSampleCount = 10;
|
||||
let _sampleRate = DEBUG ? 1 : null;
|
||||
|
||||
/**
|
||||
* A helper class for detecting when the maximem fill rate of `VirtualizedList` is exceeded.
|
||||
* By default the sampling rate is set to zero and this will do nothing. If you want to collect
|
||||
* samples (e.g. to log them), make sure to call `FillRateHelper.setSampleRate(0.0-1.0)`.
|
||||
*
|
||||
* Listeners and sample rate are global for all `VirtualizedList`s - typical usage will combine with
|
||||
* `SceneTracker.getActiveScene` to determine the context of the events.
|
||||
*/
|
||||
class FillRateHelper {
|
||||
_anyBlankStartTime = (null: ?number);
|
||||
_enabled = false;
|
||||
_getFrameMetrics: (index: number) => ?FrameMetrics;
|
||||
_info = new Info();
|
||||
_mostlyBlankStartTime = (null: ?number);
|
||||
_samplesStartTime = (null: ?number);
|
||||
|
||||
static addListener(
|
||||
callback: FillRateInfo => void,
|
||||
): {remove: () => void, ...} {
|
||||
warning(
|
||||
_sampleRate !== null,
|
||||
'Call `FillRateHelper.setSampleRate` before `addListener`.',
|
||||
);
|
||||
_listeners.push(callback);
|
||||
return {
|
||||
remove: () => {
|
||||
_listeners = _listeners.filter(listener => callback !== listener);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static setSampleRate(sampleRate: number) {
|
||||
_sampleRate = sampleRate;
|
||||
}
|
||||
|
||||
static setMinSampleCount(minSampleCount: number) {
|
||||
_minSampleCount = minSampleCount;
|
||||
}
|
||||
|
||||
constructor(getFrameMetrics: (index: number) => ?FrameMetrics) {
|
||||
this._getFrameMetrics = getFrameMetrics;
|
||||
this._enabled = (_sampleRate || 0) > Math.random();
|
||||
this._resetData();
|
||||
}
|
||||
|
||||
activate() {
|
||||
if (this._enabled && this._samplesStartTime == null) {
|
||||
DEBUG && console.debug('FillRateHelper: activate');
|
||||
this._samplesStartTime = performanceNow();
|
||||
}
|
||||
}
|
||||
|
||||
deactivateAndFlush() {
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
const start = this._samplesStartTime; // const for flow
|
||||
if (start == null) {
|
||||
DEBUG &&
|
||||
console.debug('FillRateHelper: bail on deactivate with no start time');
|
||||
return;
|
||||
}
|
||||
if (this._info.sample_count < _minSampleCount) {
|
||||
// Don't bother with under-sampled events.
|
||||
this._resetData();
|
||||
return;
|
||||
}
|
||||
const total_time_spent = performanceNow() - start;
|
||||
const info: any = {
|
||||
...this._info,
|
||||
total_time_spent,
|
||||
};
|
||||
if (DEBUG) {
|
||||
const derived = {
|
||||
avg_blankness: this._info.pixels_blank / this._info.pixels_sampled,
|
||||
avg_speed: this._info.pixels_scrolled / (total_time_spent / 1000),
|
||||
avg_speed_when_any_blank:
|
||||
this._info.any_blank_speed_sum / this._info.any_blank_count,
|
||||
any_blank_per_min:
|
||||
this._info.any_blank_count / (total_time_spent / 1000 / 60),
|
||||
any_blank_time_frac: this._info.any_blank_ms / total_time_spent,
|
||||
mostly_blank_per_min:
|
||||
this._info.mostly_blank_count / (total_time_spent / 1000 / 60),
|
||||
mostly_blank_time_frac: this._info.mostly_blank_ms / total_time_spent,
|
||||
};
|
||||
for (const key in derived) {
|
||||
derived[key] = Math.round(1000 * derived[key]) / 1000;
|
||||
}
|
||||
console.debug('FillRateHelper deactivateAndFlush: ', {derived, info});
|
||||
}
|
||||
_listeners.forEach(listener => listener(info));
|
||||
this._resetData();
|
||||
}
|
||||
|
||||
computeBlankness(
|
||||
props: {
|
||||
data: any,
|
||||
getItemCount: (data: any) => number,
|
||||
initialNumToRender: number,
|
||||
...
|
||||
},
|
||||
state: {
|
||||
first: number,
|
||||
last: number,
|
||||
...
|
||||
},
|
||||
scrollMetrics: {
|
||||
dOffset: number,
|
||||
offset: number,
|
||||
velocity: number,
|
||||
visibleLength: number,
|
||||
...
|
||||
},
|
||||
): number {
|
||||
if (
|
||||
!this._enabled ||
|
||||
props.getItemCount(props.data) === 0 ||
|
||||
this._samplesStartTime == null
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
const {dOffset, offset, velocity, visibleLength} = scrollMetrics;
|
||||
|
||||
// Denominator metrics that we track for all events - most of the time there is no blankness and
|
||||
// we want to capture that.
|
||||
this._info.sample_count++;
|
||||
this._info.pixels_sampled += Math.round(visibleLength);
|
||||
this._info.pixels_scrolled += Math.round(Math.abs(dOffset));
|
||||
const scrollSpeed = Math.round(Math.abs(velocity) * 1000); // px / sec
|
||||
|
||||
// Whether blank now or not, record the elapsed time blank if we were blank last time.
|
||||
const now = performanceNow();
|
||||
if (this._anyBlankStartTime != null) {
|
||||
this._info.any_blank_ms += now - this._anyBlankStartTime;
|
||||
}
|
||||
this._anyBlankStartTime = null;
|
||||
if (this._mostlyBlankStartTime != null) {
|
||||
this._info.mostly_blank_ms += now - this._mostlyBlankStartTime;
|
||||
}
|
||||
this._mostlyBlankStartTime = null;
|
||||
|
||||
let blankTop = 0;
|
||||
let first = state.first;
|
||||
let firstFrame = this._getFrameMetrics(first);
|
||||
while (first <= state.last && (!firstFrame || !firstFrame.inLayout)) {
|
||||
firstFrame = this._getFrameMetrics(first);
|
||||
first++;
|
||||
}
|
||||
// Only count blankTop if we aren't rendering the first item, otherwise we will count the header
|
||||
// as blank.
|
||||
if (firstFrame && first > 0) {
|
||||
blankTop = Math.min(
|
||||
visibleLength,
|
||||
Math.max(0, firstFrame.offset - offset),
|
||||
);
|
||||
}
|
||||
let blankBottom = 0;
|
||||
let last = state.last;
|
||||
let lastFrame = this._getFrameMetrics(last);
|
||||
while (last >= state.first && (!lastFrame || !lastFrame.inLayout)) {
|
||||
lastFrame = this._getFrameMetrics(last);
|
||||
last--;
|
||||
}
|
||||
// Only count blankBottom if we aren't rendering the last item, otherwise we will count the
|
||||
// footer as blank.
|
||||
if (lastFrame && last < props.getItemCount(props.data) - 1) {
|
||||
const bottomEdge = lastFrame.offset + lastFrame.length;
|
||||
blankBottom = Math.min(
|
||||
visibleLength,
|
||||
Math.max(0, offset + visibleLength - bottomEdge),
|
||||
);
|
||||
}
|
||||
const pixels_blank = Math.round(blankTop + blankBottom);
|
||||
const blankness = pixels_blank / visibleLength;
|
||||
if (blankness > 0) {
|
||||
this._anyBlankStartTime = now;
|
||||
this._info.any_blank_speed_sum += scrollSpeed;
|
||||
this._info.any_blank_count++;
|
||||
this._info.pixels_blank += pixels_blank;
|
||||
if (blankness > 0.5) {
|
||||
this._mostlyBlankStartTime = now;
|
||||
this._info.mostly_blank_count++;
|
||||
}
|
||||
} else if (scrollSpeed < 0.01 || Math.abs(dOffset) < 1) {
|
||||
this.deactivateAndFlush();
|
||||
}
|
||||
return blankness;
|
||||
}
|
||||
|
||||
enabled(): boolean {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
_resetData() {
|
||||
this._anyBlankStartTime = null;
|
||||
this._info = new Info();
|
||||
this._mostlyBlankStartTime = null;
|
||||
this._samplesStartTime = null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FillRateHelper;
|
641
node_modules/react-native/Libraries/Lists/FlatList.js
generated
vendored
Normal file
641
node_modules/react-native/Libraries/Lists/FlatList.js
generated
vendored
Normal file
@ -0,0 +1,641 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
const Platform = require('../Utilities/Platform');
|
||||
const deepDiffer = require('../Utilities/differ/deepDiffer');
|
||||
const React = require('react');
|
||||
const View = require('../Components/View/View');
|
||||
const VirtualizedList = require('./VirtualizedList');
|
||||
const StyleSheet = require('../StyleSheet/StyleSheet');
|
||||
|
||||
const invariant = require('invariant');
|
||||
|
||||
import {type ScrollResponderType} from '../Components/ScrollView/ScrollView';
|
||||
import type {ScrollViewNativeComponentType} from '../Components/ScrollView/ScrollViewNativeComponentType.js';
|
||||
import type {ViewStyleProp} from '../StyleSheet/StyleSheet';
|
||||
import type {
|
||||
ViewToken,
|
||||
ViewabilityConfigCallbackPair,
|
||||
} from './ViewabilityHelper';
|
||||
import type {RenderItemType, RenderItemProps} from './VirtualizedList';
|
||||
|
||||
type RequiredProps<ItemT> = {|
|
||||
/**
|
||||
* For simplicity, data is just a plain array. If you want to use something else, like an
|
||||
* immutable list, use the underlying `VirtualizedList` directly.
|
||||
*/
|
||||
data: ?$ReadOnlyArray<ItemT>,
|
||||
|};
|
||||
type OptionalProps<ItemT> = {|
|
||||
/**
|
||||
* Takes an item from `data` and renders it into the list. Example usage:
|
||||
*
|
||||
* <FlatList
|
||||
* ItemSeparatorComponent={Platform.OS !== 'android' && ({highlighted}) => (
|
||||
* <View style={[style.separator, highlighted && {marginLeft: 0}]} />
|
||||
* )}
|
||||
* data={[{title: 'Title Text', key: 'item1'}]}
|
||||
* renderItem={({item, separators}) => (
|
||||
* <TouchableHighlight
|
||||
* onPress={() => this._onPress(item)}
|
||||
* onShowUnderlay={separators.highlight}
|
||||
* onHideUnderlay={separators.unhighlight}>
|
||||
* <View style={{backgroundColor: 'white'}}>
|
||||
* <Text>{item.title}</Text>
|
||||
* </View>
|
||||
* </TouchableHighlight>
|
||||
* )}
|
||||
* />
|
||||
*
|
||||
* Provides additional metadata like `index` if you need it, as well as a more generic
|
||||
* `separators.updateProps` function which let's you set whatever props you want to change the
|
||||
* rendering of either the leading separator or trailing separator in case the more common
|
||||
* `highlight` and `unhighlight` (which set the `highlighted: boolean` prop) are insufficient for
|
||||
* your use-case.
|
||||
*/
|
||||
renderItem?: ?RenderItemType<ItemT>,
|
||||
|
||||
/**
|
||||
* Optional custom style for multi-item rows generated when numColumns > 1.
|
||||
*/
|
||||
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
|
||||
* `data` prop, stick it here and treat it immutably.
|
||||
*/
|
||||
extraData?: any,
|
||||
/**
|
||||
* `getItemLayout` is an optional optimizations that let us skip measurement of dynamic content if
|
||||
* you know the height of items a priori. `getItemLayout` is the most efficient, and is easy to
|
||||
* use if you have fixed height items, for example:
|
||||
*
|
||||
* getItemLayout={(data, index) => (
|
||||
* {length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index}
|
||||
* )}
|
||||
*
|
||||
* Adding `getItemLayout` can be a great performance boost for lists of several hundred items.
|
||||
* Remember to include separator length (height or width) in your offset calculation if you
|
||||
* specify `ItemSeparatorComponent`.
|
||||
*/
|
||||
getItemLayout?: (
|
||||
data: ?Array<ItemT>,
|
||||
index: number,
|
||||
) => {
|
||||
length: number,
|
||||
offset: number,
|
||||
index: number,
|
||||
...
|
||||
},
|
||||
/**
|
||||
* If true, renders items next to each other horizontally instead of stacked vertically.
|
||||
*/
|
||||
horizontal?: ?boolean,
|
||||
/**
|
||||
* How many items to render in the initial batch. This should be enough to fill the screen but not
|
||||
* much more. Note these items will never be unmounted as part of the windowed rendering in order
|
||||
* to improve perceived performance of scroll-to-top actions.
|
||||
*/
|
||||
initialNumToRender: number,
|
||||
/**
|
||||
* Instead of starting at the top with the first item, start at `initialScrollIndex`. This
|
||||
* disables the "scroll to top" optimization that keeps the first `initialNumToRender` items
|
||||
* always rendered and immediately renders the items starting at this initial index. Requires
|
||||
* `getItemLayout` to be implemented.
|
||||
*/
|
||||
initialScrollIndex?: ?number,
|
||||
/**
|
||||
* Reverses the direction of scroll. Uses scale transforms of -1.
|
||||
*/
|
||||
inverted?: ?boolean,
|
||||
/**
|
||||
* Used to extract a unique key for a given item at the specified index. Key is used for caching
|
||||
* and as the react key to track item re-ordering. The default extractor checks `item.key`, then
|
||||
* falls back to using the index, like React does.
|
||||
*/
|
||||
keyExtractor: (item: ItemT, index: number) => string,
|
||||
/**
|
||||
* Multiple columns can only be rendered with `horizontal={false}` and will zig-zag like a
|
||||
* `flexWrap` layout. Items should all be the same height - masonry layouts are not supported.
|
||||
*/
|
||||
numColumns: number,
|
||||
/**
|
||||
* See `ScrollView` for flow type and further documentation.
|
||||
*/
|
||||
fadingEdgeLength?: ?number,
|
||||
|};
|
||||
|
||||
type FlatListProps<ItemT> = {|
|
||||
...RequiredProps<ItemT>,
|
||||
...OptionalProps<ItemT>,
|
||||
|};
|
||||
|
||||
type VirtualizedListProps = React.ElementConfig<typeof VirtualizedList>;
|
||||
|
||||
export type Props<ItemT> = {
|
||||
...$Diff<
|
||||
VirtualizedListProps,
|
||||
{
|
||||
getItem: $PropertyType<VirtualizedListProps, 'getItem'>,
|
||||
getItemCount: $PropertyType<VirtualizedListProps, 'getItemCount'>,
|
||||
getItemLayout: $PropertyType<VirtualizedListProps, 'getItemLayout'>,
|
||||
renderItem: $PropertyType<VirtualizedListProps, 'renderItem'>,
|
||||
keyExtractor: $PropertyType<VirtualizedListProps, 'keyExtractor'>,
|
||||
...
|
||||
},
|
||||
>,
|
||||
...FlatListProps<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: Platform.OS === 'android',
|
||||
};
|
||||
export type DefaultProps = typeof defaultProps;
|
||||
|
||||
/**
|
||||
* A performant interface for rendering simple, flat lists, supporting the most handy features:
|
||||
*
|
||||
* - Fully cross-platform.
|
||||
* - Optional horizontal mode.
|
||||
* - Configurable viewability callbacks.
|
||||
* - Header support.
|
||||
* - Footer support.
|
||||
* - Separator support.
|
||||
* - Pull to Refresh.
|
||||
* - Scroll loading.
|
||||
* - ScrollToIndex support.
|
||||
*
|
||||
* If you need section support, use [`<SectionList>`](docs/sectionlist.html).
|
||||
*
|
||||
* Minimal Example:
|
||||
*
|
||||
* <FlatList
|
||||
* data={[{key: 'a'}, {key: 'b'}]}
|
||||
* renderItem={({item}) => <Text>{item.key}</Text>}
|
||||
* />
|
||||
*
|
||||
* More complex, multi-select example demonstrating `PureComponent` usage for perf optimization and avoiding bugs.
|
||||
*
|
||||
* - By binding the `onPressItem` handler, the props will remain `===` and `PureComponent` will
|
||||
* prevent wasteful re-renders unless the actual `id`, `selected`, or `title` props change, even
|
||||
* if the components rendered in `MyListItem` did not have such optimizations.
|
||||
* - By passing `extraData={this.state}` to `FlatList` we make sure `FlatList` itself will re-render
|
||||
* when the `state.selected` changes. Without setting this prop, `FlatList` would not know it
|
||||
* needs to re-render any items because it is also a `PureComponent` and the prop comparison will
|
||||
* not show any changes.
|
||||
* - `keyExtractor` tells the list to use the `id`s for the react keys instead of the default `key` property.
|
||||
*
|
||||
*
|
||||
* class MyListItem extends React.PureComponent {
|
||||
* _onPress = () => {
|
||||
* this.props.onPressItem(this.props.id);
|
||||
* };
|
||||
*
|
||||
* render() {
|
||||
* const textColor = this.props.selected ? "red" : "black";
|
||||
* return (
|
||||
* <TouchableOpacity onPress={this._onPress}>
|
||||
* <View>
|
||||
* <Text style={{ color: textColor }}>
|
||||
* {this.props.title}
|
||||
* </Text>
|
||||
* </View>
|
||||
* </TouchableOpacity>
|
||||
* );
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* class MultiSelectList extends React.PureComponent {
|
||||
* state = {selected: (new Map(): Map<string, boolean>)};
|
||||
*
|
||||
* _keyExtractor = (item, index) => item.id;
|
||||
*
|
||||
* _onPressItem = (id: string) => {
|
||||
* // updater functions are preferred for transactional updates
|
||||
* this.setState((state) => {
|
||||
* // copy the map rather than modifying state.
|
||||
* const selected = new Map(state.selected);
|
||||
* selected.set(id, !selected.get(id)); // toggle
|
||||
* return {selected};
|
||||
* });
|
||||
* };
|
||||
*
|
||||
* _renderItem = ({item}) => (
|
||||
* <MyListItem
|
||||
* id={item.id}
|
||||
* onPressItem={this._onPressItem}
|
||||
* selected={!!this.state.selected.get(item.id)}
|
||||
* title={item.title}
|
||||
* />
|
||||
* );
|
||||
*
|
||||
* render() {
|
||||
* return (
|
||||
* <FlatList
|
||||
* data={this.props.data}
|
||||
* extraData={this.state}
|
||||
* keyExtractor={this._keyExtractor}
|
||||
* renderItem={this._renderItem}
|
||||
* />
|
||||
* );
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* This is a convenience wrapper around [`<VirtualizedList>`](docs/virtualizedlist.html),
|
||||
* and thus inherits its props (as well as those of `ScrollView`) that aren't explicitly listed
|
||||
* here, along with the following caveats:
|
||||
*
|
||||
* - Internal state is not preserved when content scrolls out of the render window. Make sure all
|
||||
* your data is captured in the item data or external stores like Flux, Redux, or Relay.
|
||||
* - This is a `PureComponent` which means that it will not re-render if `props` remain shallow-
|
||||
* equal. Make sure that everything your `renderItem` function depends on is passed as a prop
|
||||
* (e.g. `extraData`) that is not `===` after updates, otherwise your UI may not update on
|
||||
* changes. This includes the `data` prop and parent component state.
|
||||
* - In order to constrain memory and enable smooth scrolling, content is rendered asynchronously
|
||||
* 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.
|
||||
* Alternatively, you can provide a custom `keyExtractor` prop.
|
||||
*
|
||||
* Also inherits [ScrollView Props](docs/scrollview.html#props), unless it is nested in another FlatList of same orientation.
|
||||
*/
|
||||
class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
|
||||
static defaultProps: DefaultProps = defaultProps;
|
||||
props: Props<ItemT>;
|
||||
/**
|
||||
* Scrolls to the end of the content. May be janky without `getItemLayout` prop.
|
||||
*/
|
||||
scrollToEnd(params?: ?{animated?: ?boolean, ...}) {
|
||||
if (this._listRef) {
|
||||
this._listRef.scrollToEnd(params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls to the item at the specified index such that it is positioned in the viewable area
|
||||
* such that `viewPosition` 0 places it at the top, 1 at the bottom, and 0.5 centered in the
|
||||
* middle. `viewOffset` is a fixed number of pixels to offset the final target position.
|
||||
*
|
||||
* Note: cannot scroll to locations outside the render window without specifying the
|
||||
* `getItemLayout` prop.
|
||||
*/
|
||||
scrollToIndex(params: {
|
||||
animated?: ?boolean,
|
||||
index: number,
|
||||
viewOffset?: number,
|
||||
viewPosition?: number,
|
||||
...
|
||||
}) {
|
||||
if (this._listRef) {
|
||||
this._listRef.scrollToIndex(params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requires linear scan through data - use `scrollToIndex` instead if possible.
|
||||
*
|
||||
* Note: cannot scroll to locations outside the render window without specifying the
|
||||
* `getItemLayout` prop.
|
||||
*/
|
||||
scrollToItem(params: {
|
||||
animated?: ?boolean,
|
||||
item: ItemT,
|
||||
viewPosition?: number,
|
||||
...
|
||||
}) {
|
||||
if (this._listRef) {
|
||||
this._listRef.scrollToItem(params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to a specific content pixel offset in the list.
|
||||
*
|
||||
* Check out [scrollToOffset](docs/virtualizedlist.html#scrolltooffset) of VirtualizedList
|
||||
*/
|
||||
scrollToOffset(params: {animated?: ?boolean, offset: number, ...}) {
|
||||
if (this._listRef) {
|
||||
this._listRef.scrollToOffset(params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the list an interaction has occurred, which should trigger viewability calculations, e.g.
|
||||
* if `waitForInteractions` is true and the user has not scrolled. This is typically called by
|
||||
* taps on items or by navigation actions.
|
||||
*/
|
||||
recordInteraction() {
|
||||
if (this._listRef) {
|
||||
this._listRef.recordInteraction();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the scroll indicators momentarily.
|
||||
*
|
||||
* @platform ios
|
||||
*/
|
||||
flashScrollIndicators() {
|
||||
if (this._listRef) {
|
||||
this._listRef.flashScrollIndicators();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a handle to the underlying scroll responder.
|
||||
*/
|
||||
getScrollResponder(): ?ScrollResponderType {
|
||||
if (this._listRef) {
|
||||
return this._listRef.getScrollResponder();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a reference to the underlying host component
|
||||
*/
|
||||
getNativeScrollRef():
|
||||
| ?React.ElementRef<typeof View>
|
||||
| ?React.ElementRef<ScrollViewNativeComponentType> {
|
||||
if (this._listRef) {
|
||||
return this._listRef.getScrollRef();
|
||||
}
|
||||
}
|
||||
|
||||
getScrollableNode(): any {
|
||||
if (this._listRef) {
|
||||
return this._listRef.getScrollableNode();
|
||||
}
|
||||
}
|
||||
|
||||
setNativeProps(props: {[string]: mixed, ...}) {
|
||||
if (this._listRef) {
|
||||
this._listRef.setNativeProps(props);
|
||||
}
|
||||
}
|
||||
|
||||
constructor(props: Props<ItemT>) {
|
||||
super(props);
|
||||
this._checkProps(this.props);
|
||||
if (this.props.viewabilityConfigCallbackPairs) {
|
||||
this._virtualizedListPairs = this.props.viewabilityConfigCallbackPairs.map(
|
||||
pair => ({
|
||||
viewabilityConfig: pair.viewabilityConfig,
|
||||
onViewableItemsChanged: this._createOnViewableItemsChanged(
|
||||
pair.onViewableItemsChanged,
|
||||
),
|
||||
}),
|
||||
);
|
||||
} else if (this.props.onViewableItemsChanged) {
|
||||
this._virtualizedListPairs.push({
|
||||
/* $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. */
|
||||
viewabilityConfig: this.props.viewabilityConfig,
|
||||
onViewableItemsChanged: this._createOnViewableItemsChanged(
|
||||
this.props.onViewableItemsChanged,
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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 => {
|
||||
this._listRef = ref;
|
||||
};
|
||||
|
||||
_checkProps(props: Props<ItemT>) {
|
||||
const {
|
||||
// $FlowFixMe this prop doesn't exist, is only used for an invariant
|
||||
getItem,
|
||||
// $FlowFixMe this prop doesn't exist, is only used for an invariant
|
||||
getItemCount,
|
||||
horizontal,
|
||||
numColumns,
|
||||
columnWrapperStyle,
|
||||
onViewableItemsChanged,
|
||||
viewabilityConfigCallbackPairs,
|
||||
} = props;
|
||||
invariant(
|
||||
!getItem && !getItemCount,
|
||||
'FlatList does not support custom data formats.',
|
||||
);
|
||||
if (numColumns > 1) {
|
||||
invariant(!horizontal, 'numColumns does not support horizontal.');
|
||||
} else {
|
||||
invariant(
|
||||
!columnWrapperStyle,
|
||||
'columnWrapperStyle not supported for single column lists',
|
||||
);
|
||||
}
|
||||
invariant(
|
||||
!(onViewableItemsChanged && viewabilityConfigCallbackPairs),
|
||||
'FlatList does not support setting both onViewableItemsChanged and ' +
|
||||
'viewabilityConfigCallbackPairs.',
|
||||
);
|
||||
}
|
||||
|
||||
_getItem = (data: Array<ItemT>, index: number) => {
|
||||
const {numColumns} = this.props;
|
||||
if (numColumns > 1) {
|
||||
const ret = [];
|
||||
for (let kk = 0; kk < numColumns; kk++) {
|
||||
const item = data[index * numColumns + kk];
|
||||
if (item != null) {
|
||||
ret.push(item);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
return data[index];
|
||||
}
|
||||
};
|
||||
|
||||
_getItemCount = (data: ?Array<ItemT>): number => {
|
||||
if (data) {
|
||||
const {numColumns} = this.props;
|
||||
return numColumns > 1 ? Math.ceil(data.length / numColumns) : data.length;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
_keyExtractor = (items: ItemT | Array<ItemT>, index: number) => {
|
||||
const {keyExtractor, numColumns} = this.props;
|
||||
if (numColumns > 1) {
|
||||
invariant(
|
||||
Array.isArray(items),
|
||||
'FlatList: Encountered internal consistency error, expected each item to consist of an ' +
|
||||
'array with 1-%s columns; instead, received a single item.',
|
||||
numColumns,
|
||||
);
|
||||
return items
|
||||
.map((it, kk) => keyExtractor(it, index * numColumns + kk))
|
||||
.join(':');
|
||||
} else {
|
||||
// $FlowFixMe Can't call keyExtractor with an array
|
||||
return keyExtractor(items, index);
|
||||
}
|
||||
};
|
||||
|
||||
_pushMultiColumnViewable(arr: Array<ViewToken>, v: ViewToken): void {
|
||||
const {numColumns, keyExtractor} = this.props;
|
||||
v.item.forEach((item, ii) => {
|
||||
invariant(v.index != null, 'Missing index!');
|
||||
const index = v.index * numColumns + ii;
|
||||
arr.push({...v, item, key: keyExtractor(item, index), index});
|
||||
});
|
||||
}
|
||||
|
||||
_createOnViewableItemsChanged(
|
||||
onViewableItemsChanged: ?(info: {
|
||||
viewableItems: Array<ViewToken>,
|
||||
changed: Array<ViewToken>,
|
||||
...
|
||||
}) => void,
|
||||
) {
|
||||
return (info: {
|
||||
viewableItems: Array<ViewToken>,
|
||||
changed: Array<ViewToken>,
|
||||
...
|
||||
}) => {
|
||||
const {numColumns} = this.props;
|
||||
if (onViewableItemsChanged) {
|
||||
if (numColumns > 1) {
|
||||
const changed = [];
|
||||
const viewableItems = [];
|
||||
info.viewableItems.forEach(v =>
|
||||
this._pushMultiColumnViewable(viewableItems, v),
|
||||
);
|
||||
info.changed.forEach(v => this._pushMultiColumnViewable(changed, v));
|
||||
onViewableItemsChanged({viewableItems, changed});
|
||||
} else {
|
||||
onViewableItemsChanged(info);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
_renderer = () => {
|
||||
const {
|
||||
ListItemComponent,
|
||||
renderItem,
|
||||
numColumns,
|
||||
columnWrapperStyle,
|
||||
} = this.props;
|
||||
|
||||
let virtualizedListRenderKey = ListItemComponent
|
||||
? 'ListItemComponent'
|
||||
: 'renderItem';
|
||||
|
||||
const renderer = (props): React.Node => {
|
||||
if (ListItemComponent) {
|
||||
// $FlowFixMe Component isn't valid
|
||||
return <ListItemComponent {...props} />;
|
||||
} else if (renderItem) {
|
||||
return renderItem(props);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
/* $FlowFixMe(>=0.111.0 site=react_native_fb) This comment suppresses an
|
||||
* error found when Flow v0.111 was deployed. To see the error, delete
|
||||
* this comment and run Flow. */
|
||||
[virtualizedListRenderKey]: (info: RenderItemProps<ItemT>) => {
|
||||
if (numColumns > 1) {
|
||||
const {item, index} = info;
|
||||
invariant(
|
||||
Array.isArray(item),
|
||||
'Expected array of items with numColumns > 1',
|
||||
);
|
||||
return (
|
||||
<View
|
||||
style={StyleSheet.compose(
|
||||
styles.row,
|
||||
columnWrapperStyle,
|
||||
)}>
|
||||
{item.map((it, kk) => {
|
||||
const element = renderer({
|
||||
item: it,
|
||||
index: index * numColumns + kk,
|
||||
separators: info.separators,
|
||||
});
|
||||
return element != null ? (
|
||||
<React.Fragment key={kk}>{element}</React.Fragment>
|
||||
) : null;
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
return renderer(info);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
render(): React.Node {
|
||||
const {numColumns, columnWrapperStyle, ...restProps} = this.props;
|
||||
|
||||
return (
|
||||
<VirtualizedList
|
||||
{...restProps}
|
||||
getItem={this._getItem}
|
||||
getItemCount={this._getItemCount}
|
||||
keyExtractor={this._keyExtractor}
|
||||
ref={this._captureRef}
|
||||
viewabilityConfigCallbackPairs={this._virtualizedListPairs}
|
||||
{...this._renderer()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
row: {flexDirection: 'row'},
|
||||
});
|
||||
|
||||
module.exports = FlatList;
|
264
node_modules/react-native/Libraries/Lists/SectionList.js
generated
vendored
Normal file
264
node_modules/react-native/Libraries/Lists/SectionList.js
generated
vendored
Normal file
@ -0,0 +1,264 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
const Platform = require('../Utilities/Platform');
|
||||
const React = require('react');
|
||||
const VirtualizedSectionList = require('./VirtualizedSectionList');
|
||||
|
||||
import type {ScrollResponderType} from '../Components/ScrollView/ScrollView';
|
||||
import type {
|
||||
SectionBase as _SectionBase,
|
||||
Props as VirtualizedSectionListProps,
|
||||
ScrollToLocationParamsType,
|
||||
} from './VirtualizedSectionList';
|
||||
|
||||
type Item = any;
|
||||
|
||||
export type SectionBase<SectionItemT> = _SectionBase<SectionItemT>;
|
||||
|
||||
type RequiredProps<SectionT: SectionBase<any>> = {|
|
||||
/**
|
||||
* The actual data to render, akin to the `data` prop in [`<FlatList>`](https://reactnative.dev/docs/flatlist.html).
|
||||
*
|
||||
* General shape:
|
||||
*
|
||||
* sections: $ReadOnlyArray<{
|
||||
* data: $ReadOnlyArray<SectionItem>,
|
||||
* renderItem?: ({item: SectionItem, ...}) => ?React.Element<*>,
|
||||
* ItemSeparatorComponent?: ?ReactClass<{highlighted: boolean, ...}>,
|
||||
* }>
|
||||
*/
|
||||
sections: $ReadOnlyArray<SectionT>,
|
||||
|};
|
||||
|
||||
type OptionalProps<SectionT: SectionBase<any>> = {|
|
||||
/**
|
||||
* Default renderer for every item in every section. Can be over-ridden on a per-section basis.
|
||||
*/
|
||||
renderItem?: (info: {
|
||||
item: Item,
|
||||
index: number,
|
||||
section: SectionT,
|
||||
separators: {
|
||||
highlight: () => void,
|
||||
unhighlight: () => void,
|
||||
updateProps: (select: 'leading' | 'trailing', newProps: Object) => void,
|
||||
...
|
||||
},
|
||||
...
|
||||
}) => null | React.Element<any>,
|
||||
/**
|
||||
* 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
|
||||
* `data` prop, stick it here and treat it immutably.
|
||||
*/
|
||||
extraData?: any,
|
||||
/**
|
||||
* How many items to render in the initial batch. This should be enough to fill the screen but not
|
||||
* much more. Note these items will never be unmounted as part of the windowed rendering in order
|
||||
* to improve perceived performance of scroll-to-top actions.
|
||||
*/
|
||||
initialNumToRender: number,
|
||||
/**
|
||||
* Reverses the direction of scroll. Uses scale transforms of -1.
|
||||
*/
|
||||
inverted?: ?boolean,
|
||||
/**
|
||||
* Used to extract a unique key for a given item at the specified index. Key is used for caching
|
||||
* and as the react key to track item re-ordering. The default extractor checks item.key, then
|
||||
* falls back to using the index, like react does. Note that this sets keys for each item, but
|
||||
* each overall section still needs its own key.
|
||||
*/
|
||||
keyExtractor: (item: Item, index: number) => string,
|
||||
/**
|
||||
* Called once when the scroll position gets within `onEndReachedThreshold` of the rendered
|
||||
* content.
|
||||
*/
|
||||
onEndReached?: ?(info: {distanceFromEnd: number, ...}) => void,
|
||||
/**
|
||||
* Note: may have bugs (missing content) in some circumstances - use at your own risk.
|
||||
*
|
||||
* This may improve scroll performance for large lists.
|
||||
*/
|
||||
removeClippedSubviews?: boolean,
|
||||
|};
|
||||
|
||||
export type Props<SectionT> = {|
|
||||
...$Diff<
|
||||
VirtualizedSectionListProps<SectionT>,
|
||||
{
|
||||
getItem: $PropertyType<VirtualizedSectionListProps<SectionT>, 'getItem'>,
|
||||
getItemCount: $PropertyType<
|
||||
VirtualizedSectionListProps<SectionT>,
|
||||
'getItemCount',
|
||||
>,
|
||||
renderItem: $PropertyType<
|
||||
VirtualizedSectionListProps<SectionT>,
|
||||
'renderItem',
|
||||
>,
|
||||
...
|
||||
},
|
||||
>,
|
||||
...RequiredProps<SectionT>,
|
||||
...OptionalProps<SectionT>,
|
||||
|};
|
||||
|
||||
const defaultProps = {
|
||||
...VirtualizedSectionList.defaultProps,
|
||||
stickySectionHeadersEnabled: Platform.OS === 'ios',
|
||||
};
|
||||
|
||||
type DefaultProps = typeof defaultProps;
|
||||
|
||||
/**
|
||||
* A performant interface for rendering sectioned lists, supporting the most handy features:
|
||||
*
|
||||
* - Fully cross-platform.
|
||||
* - Configurable viewability callbacks.
|
||||
* - List header support.
|
||||
* - List footer support.
|
||||
* - Item separator support.
|
||||
* - Section header support.
|
||||
* - Section separator support.
|
||||
* - Heterogeneous data and item rendering support.
|
||||
* - Pull to Refresh.
|
||||
* - Scroll loading.
|
||||
*
|
||||
* If you don't need section support and want a simpler interface, use
|
||||
* [`<FlatList>`](https://reactnative.dev/docs/flatlist.html).
|
||||
*
|
||||
* Simple Examples:
|
||||
*
|
||||
* <SectionList
|
||||
* renderItem={({item}) => <ListItem title={item} />}
|
||||
* renderSectionHeader={({section}) => <Header title={section.title} />}
|
||||
* sections={[ // homogeneous rendering between sections
|
||||
* {data: [...], title: ...},
|
||||
* {data: [...], title: ...},
|
||||
* {data: [...], title: ...},
|
||||
* ]}
|
||||
* />
|
||||
*
|
||||
* <SectionList
|
||||
* sections={[ // heterogeneous rendering between sections
|
||||
* {data: [...], renderItem: ...},
|
||||
* {data: [...], renderItem: ...},
|
||||
* {data: [...], renderItem: ...},
|
||||
* ]}
|
||||
* />
|
||||
*
|
||||
* This is a convenience wrapper around [`<VirtualizedList>`](docs/virtualizedlist.html),
|
||||
* and thus inherits its props (as well as those of `ScrollView`) that aren't explicitly listed
|
||||
* here, along with the following caveats:
|
||||
*
|
||||
* - Internal state is not preserved when content scrolls out of the render window. Make sure all
|
||||
* your data is captured in the item data or external stores like Flux, Redux, or Relay.
|
||||
* - This is a `PureComponent` which means that it will not re-render if `props` remain shallow-
|
||||
* equal. Make sure that everything your `renderItem` function depends on is passed as a prop
|
||||
* (e.g. `extraData`) that is not `===` after updates, otherwise your UI may not update on
|
||||
* changes. This includes the `data` prop and parent component state.
|
||||
* - In order to constrain memory and enable smooth scrolling, content is rendered asynchronously
|
||||
* offscreen. This means it's possible to scroll faster than the fill rate and 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.
|
||||
* Alternatively, you can provide a custom `keyExtractor` prop.
|
||||
*
|
||||
*/
|
||||
class SectionList<SectionT: SectionBase<any>> extends React.PureComponent<
|
||||
Props<SectionT>,
|
||||
void,
|
||||
> {
|
||||
props: Props<SectionT>;
|
||||
static defaultProps: DefaultProps = defaultProps;
|
||||
|
||||
/**
|
||||
* Scrolls to the item at the specified `sectionIndex` and `itemIndex` (within the section)
|
||||
* positioned in the viewable area such that `viewPosition` 0 places it at the top (and may be
|
||||
* covered by a sticky header), 1 at the bottom, and 0.5 centered in the middle. `viewOffset` is a
|
||||
* fixed number of pixels to offset the final target position, e.g. to compensate for sticky
|
||||
* headers.
|
||||
*
|
||||
* Note: cannot scroll to locations outside the render window without specifying the
|
||||
* `getItemLayout` prop.
|
||||
*/
|
||||
scrollToLocation(params: ScrollToLocationParamsType) {
|
||||
if (this._wrapperListRef != null) {
|
||||
this._wrapperListRef.scrollToLocation(params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the list an interaction has occurred, which should trigger viewability calculations, e.g.
|
||||
* if `waitForInteractions` is true and the user has not scrolled. This is typically called by
|
||||
* taps on items or by navigation actions.
|
||||
*/
|
||||
recordInteraction() {
|
||||
const listRef = this._wrapperListRef && this._wrapperListRef.getListRef();
|
||||
listRef && listRef.recordInteraction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the scroll indicators momentarily.
|
||||
*
|
||||
* @platform ios
|
||||
*/
|
||||
flashScrollIndicators() {
|
||||
const listRef = this._wrapperListRef && this._wrapperListRef.getListRef();
|
||||
listRef && listRef.flashScrollIndicators();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a handle to the underlying scroll responder.
|
||||
*/
|
||||
getScrollResponder(): ?ScrollResponderType {
|
||||
const listRef = this._wrapperListRef && this._wrapperListRef.getListRef();
|
||||
if (listRef) {
|
||||
return listRef.getScrollResponder();
|
||||
}
|
||||
}
|
||||
|
||||
getScrollableNode(): any {
|
||||
const listRef = this._wrapperListRef && this._wrapperListRef.getListRef();
|
||||
if (listRef) {
|
||||
return listRef.getScrollableNode();
|
||||
}
|
||||
}
|
||||
|
||||
setNativeProps(props: Object) {
|
||||
const listRef = this._wrapperListRef && this._wrapperListRef.getListRef();
|
||||
if (listRef) {
|
||||
listRef.setNativeProps(props);
|
||||
}
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
return (
|
||||
<VirtualizedSectionList
|
||||
{...this.props}
|
||||
ref={this._captureRef}
|
||||
getItemCount={items => items.length}
|
||||
getItem={(items, index) => items[index]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
_wrapperListRef: ?React.ElementRef<typeof VirtualizedSectionList>;
|
||||
_captureRef = ref => {
|
||||
/* $FlowFixMe(>=0.99.0 site=react_native_fb) This comment suppresses an
|
||||
* error found when Flow v0.99 was deployed. To see the error, delete this
|
||||
* comment and run Flow. */
|
||||
this._wrapperListRef = ref;
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = SectionList;
|
341
node_modules/react-native/Libraries/Lists/ViewabilityHelper.js
generated
vendored
Normal file
341
node_modules/react-native/Libraries/Lists/ViewabilityHelper.js
generated
vendored
Normal file
@ -0,0 +1,341 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
const invariant = require('invariant');
|
||||
|
||||
export type ViewToken = {
|
||||
item: any,
|
||||
key: string,
|
||||
index: ?number,
|
||||
isViewable: boolean,
|
||||
section?: any,
|
||||
...
|
||||
};
|
||||
|
||||
export type ViewabilityConfigCallbackPair = {
|
||||
viewabilityConfig: ViewabilityConfig,
|
||||
onViewableItemsChanged: (info: {
|
||||
viewableItems: Array<ViewToken>,
|
||||
changed: Array<ViewToken>,
|
||||
...
|
||||
}) => void,
|
||||
...
|
||||
};
|
||||
|
||||
export type ViewabilityConfig = {|
|
||||
/**
|
||||
* Minimum amount of time (in milliseconds) that an item must be physically viewable before the
|
||||
* viewability callback will be fired. A high number means that scrolling through content without
|
||||
* stopping will not mark the content as viewable.
|
||||
*/
|
||||
minimumViewTime?: number,
|
||||
|
||||
/**
|
||||
* Percent of viewport that must be covered for a partially occluded item to count as
|
||||
* "viewable", 0-100. Fully visible items are always considered viewable. A value of 0 means
|
||||
* that a single pixel in the viewport makes the item viewable, and a value of 100 means that
|
||||
* an item must be either entirely visible or cover the entire viewport to count as viewable.
|
||||
*/
|
||||
viewAreaCoveragePercentThreshold?: number,
|
||||
|
||||
/**
|
||||
* Similar to `viewAreaPercentThreshold`, but considers the percent of the item that is visible,
|
||||
* rather than the fraction of the viewable area it covers.
|
||||
*/
|
||||
itemVisiblePercentThreshold?: number,
|
||||
|
||||
/**
|
||||
* Nothing is considered viewable until the user scrolls or `recordInteraction` is called after
|
||||
* render.
|
||||
*/
|
||||
waitForInteraction?: boolean,
|
||||
|};
|
||||
|
||||
/**
|
||||
* A Utility class for calculating viewable items based on current metrics like scroll position and
|
||||
* layout.
|
||||
*
|
||||
* An item is said to be in a "viewable" state when any of the following
|
||||
* is true for longer than `minimumViewTime` milliseconds (after an interaction if `waitForInteraction`
|
||||
* is true):
|
||||
*
|
||||
* - Occupying >= `viewAreaCoveragePercentThreshold` of the view area XOR fraction of the item
|
||||
* visible in the view area >= `itemVisiblePercentThreshold`.
|
||||
* - Entirely visible on screen
|
||||
*/
|
||||
class ViewabilityHelper {
|
||||
_config: ViewabilityConfig;
|
||||
_hasInteracted: boolean = false;
|
||||
_timers: Set<number> = new Set();
|
||||
_viewableIndices: Array<number> = [];
|
||||
_viewableItems: Map<string, ViewToken> = new Map();
|
||||
|
||||
constructor(
|
||||
config: ViewabilityConfig = {viewAreaCoveragePercentThreshold: 0},
|
||||
) {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup, e.g. on unmount. Clears any pending timers.
|
||||
*/
|
||||
dispose() {
|
||||
/* $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._timers.forEach(clearTimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which items are viewable based on the current metrics and config.
|
||||
*/
|
||||
computeViewableItems(
|
||||
itemCount: number,
|
||||
scrollOffset: number,
|
||||
viewportHeight: number,
|
||||
getFrameMetrics: (
|
||||
index: number,
|
||||
) => ?{
|
||||
length: number,
|
||||
offset: number,
|
||||
...
|
||||
},
|
||||
// Optional optimization to reduce the scan size
|
||||
renderRange?: {
|
||||
first: number,
|
||||
last: number,
|
||||
...
|
||||
},
|
||||
): Array<number> {
|
||||
const {
|
||||
itemVisiblePercentThreshold,
|
||||
viewAreaCoveragePercentThreshold,
|
||||
} = this._config;
|
||||
const viewAreaMode = viewAreaCoveragePercentThreshold != null;
|
||||
const viewablePercentThreshold = viewAreaMode
|
||||
? viewAreaCoveragePercentThreshold
|
||||
: itemVisiblePercentThreshold;
|
||||
invariant(
|
||||
viewablePercentThreshold != null &&
|
||||
(itemVisiblePercentThreshold != null) !==
|
||||
(viewAreaCoveragePercentThreshold != null),
|
||||
'Must set exactly one of itemVisiblePercentThreshold or viewAreaCoveragePercentThreshold',
|
||||
);
|
||||
const viewableIndices = [];
|
||||
if (itemCount === 0) {
|
||||
return viewableIndices;
|
||||
}
|
||||
let firstVisible = -1;
|
||||
const {first, last} = renderRange || {first: 0, last: itemCount - 1};
|
||||
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) {
|
||||
continue;
|
||||
}
|
||||
const top = metrics.offset - scrollOffset;
|
||||
const bottom = top + metrics.length;
|
||||
if (top < viewportHeight && bottom > 0) {
|
||||
firstVisible = idx;
|
||||
if (
|
||||
_isViewable(
|
||||
viewAreaMode,
|
||||
viewablePercentThreshold,
|
||||
top,
|
||||
bottom,
|
||||
viewportHeight,
|
||||
metrics.length,
|
||||
)
|
||||
) {
|
||||
viewableIndices.push(idx);
|
||||
}
|
||||
} else if (firstVisible >= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return viewableIndices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Figures out which items are viewable and how that has changed from before and calls
|
||||
* `onViewableItemsChanged` as appropriate.
|
||||
*/
|
||||
onUpdate(
|
||||
itemCount: number,
|
||||
scrollOffset: number,
|
||||
viewportHeight: number,
|
||||
getFrameMetrics: (
|
||||
index: number,
|
||||
) => ?{
|
||||
length: number,
|
||||
offset: number,
|
||||
...
|
||||
},
|
||||
createViewToken: (index: number, isViewable: boolean) => ViewToken,
|
||||
onViewableItemsChanged: ({
|
||||
viewableItems: Array<ViewToken>,
|
||||
changed: Array<ViewToken>,
|
||||
...
|
||||
}) => void,
|
||||
// Optional optimization to reduce the scan size
|
||||
renderRange?: {
|
||||
first: number,
|
||||
last: number,
|
||||
...
|
||||
},
|
||||
): void {
|
||||
if (
|
||||
(this._config.waitForInteraction && !this._hasInteracted) ||
|
||||
itemCount === 0 ||
|
||||
!getFrameMetrics(0)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let viewableIndices = [];
|
||||
if (itemCount) {
|
||||
viewableIndices = this.computeViewableItems(
|
||||
itemCount,
|
||||
scrollOffset,
|
||||
viewportHeight,
|
||||
getFrameMetrics,
|
||||
renderRange,
|
||||
);
|
||||
}
|
||||
if (
|
||||
this._viewableIndices.length === viewableIndices.length &&
|
||||
this._viewableIndices.every((v, ii) => v === viewableIndices[ii])
|
||||
) {
|
||||
// We might get a lot of scroll events where visibility doesn't change and we don't want to do
|
||||
// extra work in those cases.
|
||||
return;
|
||||
}
|
||||
this._viewableIndices = viewableIndices;
|
||||
if (this._config.minimumViewTime) {
|
||||
const handle = setTimeout(() => {
|
||||
/* $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._timers.delete(handle);
|
||||
this._onUpdateSync(
|
||||
viewableIndices,
|
||||
onViewableItemsChanged,
|
||||
createViewToken,
|
||||
);
|
||||
}, this._config.minimumViewTime);
|
||||
/* $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._timers.add(handle);
|
||||
} else {
|
||||
this._onUpdateSync(
|
||||
viewableIndices,
|
||||
onViewableItemsChanged,
|
||||
createViewToken,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* clean-up cached _viewableIndices to evaluate changed items on next update
|
||||
*/
|
||||
resetViewableIndices() {
|
||||
this._viewableIndices = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Records that an interaction has happened even if there has been no scroll.
|
||||
*/
|
||||
recordInteraction() {
|
||||
this._hasInteracted = true;
|
||||
}
|
||||
|
||||
_onUpdateSync(
|
||||
viewableIndicesToCheck,
|
||||
onViewableItemsChanged,
|
||||
createViewToken,
|
||||
) {
|
||||
// Filter out indices that have gone out of view since this call was scheduled.
|
||||
viewableIndicesToCheck = viewableIndicesToCheck.filter(ii =>
|
||||
this._viewableIndices.includes(ii),
|
||||
);
|
||||
const prevItems = this._viewableItems;
|
||||
const nextItems = new Map(
|
||||
viewableIndicesToCheck.map(ii => {
|
||||
const viewable = createViewToken(ii, true);
|
||||
return [viewable.key, viewable];
|
||||
}),
|
||||
);
|
||||
|
||||
const changed = [];
|
||||
for (const [key, viewable] of nextItems) {
|
||||
if (!prevItems.has(key)) {
|
||||
changed.push(viewable);
|
||||
}
|
||||
}
|
||||
for (const [key, viewable] of prevItems) {
|
||||
if (!nextItems.has(key)) {
|
||||
changed.push({...viewable, isViewable: false});
|
||||
}
|
||||
}
|
||||
if (changed.length > 0) {
|
||||
this._viewableItems = nextItems;
|
||||
onViewableItemsChanged({
|
||||
viewableItems: Array.from(nextItems.values()),
|
||||
changed,
|
||||
viewabilityConfig: this._config,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _isViewable(
|
||||
viewAreaMode: boolean,
|
||||
viewablePercentThreshold: number,
|
||||
top: number,
|
||||
bottom: number,
|
||||
viewportHeight: number,
|
||||
itemLength: number,
|
||||
): boolean {
|
||||
if (_isEntirelyVisible(top, bottom, viewportHeight)) {
|
||||
return true;
|
||||
} else {
|
||||
const pixels = _getPixelsVisible(top, bottom, viewportHeight);
|
||||
const percent =
|
||||
100 * (viewAreaMode ? pixels / viewportHeight : pixels / itemLength);
|
||||
return percent >= viewablePercentThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
function _getPixelsVisible(
|
||||
top: number,
|
||||
bottom: number,
|
||||
viewportHeight: number,
|
||||
): number {
|
||||
const visibleHeight = Math.min(bottom, viewportHeight) - Math.max(top, 0);
|
||||
return Math.max(0, visibleHeight);
|
||||
}
|
||||
|
||||
function _isEntirelyVisible(
|
||||
top: number,
|
||||
bottom: number,
|
||||
viewportHeight: number,
|
||||
): boolean {
|
||||
return top >= 0 && bottom <= viewportHeight && bottom > top;
|
||||
}
|
||||
|
||||
module.exports = ViewabilityHelper;
|
247
node_modules/react-native/Libraries/Lists/VirtualizeUtils.js
generated
vendored
Normal file
247
node_modules/react-native/Libraries/Lists/VirtualizeUtils.js
generated
vendored
Normal file
@ -0,0 +1,247 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
const invariant = require('invariant');
|
||||
|
||||
/**
|
||||
* Used to find the indices of the frames that overlap the given offsets. Useful for finding the
|
||||
* items that bound different windows of content, such as the visible area or the buffered overscan
|
||||
* area.
|
||||
*/
|
||||
function elementsThatOverlapOffsets(
|
||||
offsets: Array<number>,
|
||||
itemCount: number,
|
||||
getFrameMetrics: (
|
||||
index: number,
|
||||
) => {
|
||||
length: number,
|
||||
offset: number,
|
||||
...
|
||||
},
|
||||
): Array<number> {
|
||||
const out = [];
|
||||
let outLength = 0;
|
||||
for (let ii = 0; ii < itemCount; ii++) {
|
||||
const frame = getFrameMetrics(ii);
|
||||
const trailingOffset = frame.offset + frame.length;
|
||||
for (let kk = 0; kk < offsets.length; kk++) {
|
||||
if (out[kk] == null && trailingOffset >= offsets[kk]) {
|
||||
out[kk] = ii;
|
||||
outLength++;
|
||||
if (kk === offsets.length - 1) {
|
||||
invariant(
|
||||
outLength === offsets.length,
|
||||
'bad offsets input, should be in increasing order: %s',
|
||||
JSON.stringify(offsets),
|
||||
);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the number of elements in the `next` range that are new compared to the `prev` range.
|
||||
* Handy for calculating how many new items will be rendered when the render window changes so we
|
||||
* can restrict the number of new items render at once so that content can appear on the screen
|
||||
* faster.
|
||||
*/
|
||||
function newRangeCount(
|
||||
prev: {
|
||||
first: number,
|
||||
last: number,
|
||||
...
|
||||
},
|
||||
next: {
|
||||
first: number,
|
||||
last: number,
|
||||
...
|
||||
},
|
||||
): number {
|
||||
return (
|
||||
next.last -
|
||||
next.first +
|
||||
1 -
|
||||
Math.max(
|
||||
0,
|
||||
1 + Math.min(next.last, prev.last) - Math.max(next.first, prev.first),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom logic for determining which items should be rendered given the current frame and scroll
|
||||
* metrics, as well as the previous render state. The algorithm may evolve over time, but generally
|
||||
* prioritizes the visible area first, then expands that with overscan regions ahead and behind,
|
||||
* biased in the direction of scroll.
|
||||
*/
|
||||
function computeWindowedRenderLimits(
|
||||
props: {
|
||||
data: any,
|
||||
getItemCount: (data: any) => number,
|
||||
maxToRenderPerBatch: number,
|
||||
windowSize: number,
|
||||
...
|
||||
},
|
||||
prev: {
|
||||
first: number,
|
||||
last: number,
|
||||
...
|
||||
},
|
||||
getFrameMetricsApprox: (
|
||||
index: number,
|
||||
) => {
|
||||
length: number,
|
||||
offset: number,
|
||||
...
|
||||
},
|
||||
scrollMetrics: {
|
||||
dt: number,
|
||||
offset: number,
|
||||
velocity: number,
|
||||
visibleLength: number,
|
||||
...
|
||||
},
|
||||
): {
|
||||
first: number,
|
||||
last: number,
|
||||
...
|
||||
} {
|
||||
const {data, getItemCount, maxToRenderPerBatch, windowSize} = props;
|
||||
const itemCount = getItemCount(data);
|
||||
if (itemCount === 0) {
|
||||
return prev;
|
||||
}
|
||||
const {offset, velocity, visibleLength} = scrollMetrics;
|
||||
|
||||
// Start with visible area, then compute maximum overscan region by expanding from there, biased
|
||||
// in the direction of scroll. Total overscan area is capped, which should cap memory consumption
|
||||
// too.
|
||||
const visibleBegin = Math.max(0, offset);
|
||||
const visibleEnd = visibleBegin + visibleLength;
|
||||
const overscanLength = (windowSize - 1) * visibleLength;
|
||||
|
||||
// Considering velocity seems to introduce more churn than it's worth.
|
||||
const leadFactor = 0.5; // Math.max(0, Math.min(1, velocity / 25 + 0.5));
|
||||
|
||||
const fillPreference =
|
||||
velocity > 1 ? 'after' : velocity < -1 ? 'before' : 'none';
|
||||
|
||||
const overscanBegin = Math.max(
|
||||
0,
|
||||
visibleBegin - (1 - leadFactor) * overscanLength,
|
||||
);
|
||||
const overscanEnd = Math.max(0, visibleEnd + leadFactor * overscanLength);
|
||||
|
||||
const lastItemOffset = getFrameMetricsApprox(itemCount - 1).offset;
|
||||
if (lastItemOffset < overscanBegin) {
|
||||
// Entire list is before our overscan window
|
||||
return {
|
||||
first: Math.max(0, itemCount - 1 - maxToRenderPerBatch),
|
||||
last: itemCount - 1,
|
||||
};
|
||||
}
|
||||
|
||||
// Find the indices that correspond to the items at the render boundaries we're targeting.
|
||||
let [overscanFirst, first, last, overscanLast] = elementsThatOverlapOffsets(
|
||||
[overscanBegin, visibleBegin, visibleEnd, overscanEnd],
|
||||
props.getItemCount(props.data),
|
||||
getFrameMetricsApprox,
|
||||
);
|
||||
overscanFirst = overscanFirst == null ? 0 : overscanFirst;
|
||||
first = first == null ? Math.max(0, overscanFirst) : first;
|
||||
overscanLast = overscanLast == null ? itemCount - 1 : overscanLast;
|
||||
last =
|
||||
last == null
|
||||
? Math.min(overscanLast, first + maxToRenderPerBatch - 1)
|
||||
: last;
|
||||
const visible = {first, last};
|
||||
|
||||
// We want to limit the number of new cells we're rendering per batch so that we can fill the
|
||||
// content on the screen quickly. If we rendered the entire overscan window at once, the user
|
||||
// could be staring at white space for a long time waiting for a bunch of offscreen content to
|
||||
// render.
|
||||
let newCellCount = newRangeCount(prev, visible);
|
||||
|
||||
while (true) {
|
||||
if (first <= overscanFirst && last >= overscanLast) {
|
||||
// If we fill the entire overscan range, we're done.
|
||||
break;
|
||||
}
|
||||
const maxNewCells = newCellCount >= maxToRenderPerBatch;
|
||||
const firstWillAddMore = first <= prev.first || first > prev.last;
|
||||
const firstShouldIncrement =
|
||||
first > overscanFirst && (!maxNewCells || !firstWillAddMore);
|
||||
const lastWillAddMore = last >= prev.last || last < prev.first;
|
||||
const lastShouldIncrement =
|
||||
last < overscanLast && (!maxNewCells || !lastWillAddMore);
|
||||
if (maxNewCells && !firstShouldIncrement && !lastShouldIncrement) {
|
||||
// We only want to stop if we've hit maxNewCells AND we cannot increment first or last
|
||||
// without rendering new items. This let's us preserve as many already rendered items as
|
||||
// possible, reducing render churn and keeping the rendered overscan range as large as
|
||||
// possible.
|
||||
break;
|
||||
}
|
||||
if (
|
||||
firstShouldIncrement &&
|
||||
!(fillPreference === 'after' && lastShouldIncrement && lastWillAddMore)
|
||||
) {
|
||||
if (firstWillAddMore) {
|
||||
newCellCount++;
|
||||
}
|
||||
first--;
|
||||
}
|
||||
if (
|
||||
lastShouldIncrement &&
|
||||
!(fillPreference === 'before' && firstShouldIncrement && firstWillAddMore)
|
||||
) {
|
||||
if (lastWillAddMore) {
|
||||
newCellCount++;
|
||||
}
|
||||
last++;
|
||||
}
|
||||
}
|
||||
if (
|
||||
!(
|
||||
last >= first &&
|
||||
first >= 0 &&
|
||||
last < itemCount &&
|
||||
first >= overscanFirst &&
|
||||
last <= overscanLast &&
|
||||
first <= visible.first &&
|
||||
last >= visible.last
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
'Bad window calculation ' +
|
||||
JSON.stringify({
|
||||
first,
|
||||
last,
|
||||
itemCount,
|
||||
overscanFirst,
|
||||
overscanLast,
|
||||
visible,
|
||||
}),
|
||||
);
|
||||
}
|
||||
return {first, last};
|
||||
}
|
||||
|
||||
const VirtualizeUtils = {
|
||||
computeWindowedRenderLimits,
|
||||
elementsThatOverlapOffsets,
|
||||
newRangeCount,
|
||||
};
|
||||
|
||||
module.exports = VirtualizeUtils;
|
2233
node_modules/react-native/Libraries/Lists/VirtualizedList.js
generated
vendored
Normal file
2233
node_modules/react-native/Libraries/Lists/VirtualizedList.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
578
node_modules/react-native/Libraries/Lists/VirtualizedSectionList.js
generated
vendored
Normal file
578
node_modules/react-native/Libraries/Lists/VirtualizedSectionList.js
generated
vendored
Normal file
@ -0,0 +1,578 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
const React = require('react');
|
||||
const View = require('../Components/View/View');
|
||||
const VirtualizedList = require('./VirtualizedList');
|
||||
|
||||
const invariant = require('invariant');
|
||||
|
||||
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,
|
||||
...
|
||||
},
|
||||
...
|
||||
}) => null | React.Element<any>,
|
||||
ItemSeparatorComponent?: ?React.ComponentType<any>,
|
||||
keyExtractor?: (item: SectionItemT, index?: ?number) => string,
|
||||
...
|
||||
};
|
||||
|
||||
type RequiredProps<SectionT: SectionBase<any>> = {|
|
||||
sections: $ReadOnlyArray<SectionT>,
|
||||
|};
|
||||
|
||||
type OptionalProps<SectionT: SectionBase<any>> = {|
|
||||
/**
|
||||
* Default renderer for every item in every section.
|
||||
*/
|
||||
renderItem?: (info: {
|
||||
item: Item,
|
||||
index: number,
|
||||
section: SectionT,
|
||||
separators: {
|
||||
highlight: () => void,
|
||||
unhighlight: () => void,
|
||||
updateProps: (select: 'leading' | 'trailing', newProps: Object) => void,
|
||||
...
|
||||
},
|
||||
...
|
||||
}) => null | React.Element<any>,
|
||||
/**
|
||||
* Rendered at the top of each section. These stick to the top of the `ScrollView` by default on
|
||||
* iOS. See `stickySectionHeadersEnabled`.
|
||||
*/
|
||||
renderSectionHeader?: ?(info: {
|
||||
section: SectionT,
|
||||
...
|
||||
}) => null | React.Element<any>,
|
||||
/**
|
||||
* Rendered at the bottom of each section.
|
||||
*/
|
||||
renderSectionFooter?: ?(info: {
|
||||
section: SectionT,
|
||||
...
|
||||
}) => null | React.Element<any>,
|
||||
/**
|
||||
* Rendered at the top and bottom of each section (note this is different from
|
||||
* `ItemSeparatorComponent` which is only rendered between items). These are intended to separate
|
||||
* sections from the headers above and below and typically have the same highlight response as
|
||||
* `ItemSeparatorComponent`. Also receives `highlighted`, `[leading/trailing][Item/Separator]`,
|
||||
* and any custom props from `separators.updateProps`.
|
||||
*/
|
||||
SectionSeparatorComponent?: ?React.ComponentType<any>,
|
||||
/**
|
||||
* Makes section headers stick to the top of the screen until the next one pushes it off. Only
|
||||
* enabled by default on iOS because that is the platform standard there.
|
||||
*/
|
||||
stickySectionHeadersEnabled?: boolean,
|
||||
onEndReached?: ?({distanceFromEnd: number, ...}) => void,
|
||||
|};
|
||||
|
||||
type VirtualizedListProps = React.ElementProps<typeof VirtualizedList>;
|
||||
|
||||
export type Props<SectionT> = {|
|
||||
...RequiredProps<SectionT>,
|
||||
...OptionalProps<SectionT>,
|
||||
...$Diff<
|
||||
VirtualizedListProps,
|
||||
{renderItem: $PropertyType<VirtualizedListProps, 'renderItem'>, ...},
|
||||
>,
|
||||
|};
|
||||
export type ScrollToLocationParamsType = {|
|
||||
animated?: ?boolean,
|
||||
itemIndex: number,
|
||||
sectionIndex: number,
|
||||
viewOffset?: number,
|
||||
viewPosition?: number,
|
||||
|};
|
||||
|
||||
type DefaultProps = {|
|
||||
...typeof VirtualizedList.defaultProps,
|
||||
data: $ReadOnlyArray<Item>,
|
||||
|};
|
||||
|
||||
type State = {childProps: VirtualizedListProps, ...};
|
||||
|
||||
/**
|
||||
* Right now this just flattens everything into one list and uses VirtualizedList under the
|
||||
* 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<any>,
|
||||
> extends React.PureComponent<Props<SectionT>, State> {
|
||||
static defaultProps: DefaultProps = {
|
||||
...VirtualizedList.defaultProps,
|
||||
data: [],
|
||||
};
|
||||
|
||||
scrollToLocation(params: ScrollToLocationParamsType) {
|
||||
let index = params.itemIndex;
|
||||
for (let i = 0; i < params.sectionIndex; i++) {
|
||||
index += this.props.getItemCount(this.props.sections[i].data) + 2;
|
||||
}
|
||||
let viewOffset = params.viewOffset || 0;
|
||||
if (params.itemIndex > 0 && this.props.stickySectionHeadersEnabled) {
|
||||
// $FlowFixMe Cannot access private property
|
||||
const frame = this._listRef._getFrameMetricsApprox(
|
||||
index - params.itemIndex,
|
||||
);
|
||||
viewOffset += frame.length;
|
||||
}
|
||||
const toIndexParams = {
|
||||
...params,
|
||||
viewOffset,
|
||||
index,
|
||||
};
|
||||
this._listRef.scrollToIndex(toIndexParams);
|
||||
}
|
||||
|
||||
getListRef(): VirtualizedList {
|
||||
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;
|
||||
|
||||
const {
|
||||
SectionSeparatorComponent,
|
||||
renderItem,
|
||||
renderSectionFooter,
|
||||
renderSectionHeader,
|
||||
sections: _sections,
|
||||
stickySectionHeadersEnabled,
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
return {
|
||||
childProps: {
|
||||
...restProps,
|
||||
renderItem: this._renderItem,
|
||||
ItemSeparatorComponent: undefined, // Rendered with renderItem
|
||||
data: props.sections,
|
||||
getItemCount: () => itemCount,
|
||||
// $FlowFixMe
|
||||
getItem: (sections, index) => this._getItem(props, sections, index),
|
||||
keyExtractor: this._keyExtractor,
|
||||
onViewableItemsChanged: props.onViewableItemsChanged
|
||||
? this._onViewableItemsChanged
|
||||
: undefined,
|
||||
stickyHeaderIndices: props.stickySectionHeadersEnabled
|
||||
? stickyHeaderIndices
|
||||
: undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
return (
|
||||
<VirtualizedList {...this.state.childProps} ref={this._captureRef} />
|
||||
);
|
||||
}
|
||||
|
||||
_getItem = (
|
||||
props: Props<SectionT>,
|
||||
sections: ?$ReadOnlyArray<Item>,
|
||||
index: number,
|
||||
): ?Item => {
|
||||
if (!sections) {
|
||||
return null;
|
||||
}
|
||||
let itemIdx = index - 1;
|
||||
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 section;
|
||||
} else if (itemIdx < itemCount) {
|
||||
// If we are in the bounds of the list's data then return the item.
|
||||
return props.getItem(sectionData, itemIdx);
|
||||
} else {
|
||||
itemIdx -= itemCount + 2; // Add two for the header and footer
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
_keyExtractor = (item: Item, index: number) => {
|
||||
const info = this._subExtractor(index);
|
||||
return (info && info.key) || String(index);
|
||||
};
|
||||
|
||||
_subExtractor(
|
||||
index: number,
|
||||
): ?{
|
||||
section: SectionT,
|
||||
// Key of the section or combined key for section + item
|
||||
key: string,
|
||||
// Relative index within the section
|
||||
index: ?number,
|
||||
// True if this is the section header
|
||||
header?: ?boolean,
|
||||
leadingItem?: ?Item,
|
||||
leadingSection?: ?SectionT,
|
||||
trailingItem?: ?Item,
|
||||
trailingSection?: ?SectionT,
|
||||
...
|
||||
} {
|
||||
let itemIndex = index;
|
||||
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 >= 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: sections[i + 1],
|
||||
};
|
||||
} else if (itemIndex === getItemCount(sectionData)) {
|
||||
return {
|
||||
section,
|
||||
key: key + ':footer',
|
||||
index: null,
|
||||
header: false,
|
||||
trailingSection: sections[i + 1],
|
||||
};
|
||||
} else {
|
||||
const extractor = section.keyExtractor || keyExtractor;
|
||||
return {
|
||||
section,
|
||||
key:
|
||||
key + ':' + extractor(getItem(sectionData, itemIndex), itemIndex),
|
||||
index: itemIndex,
|
||||
leadingItem: getItem(sectionData, itemIndex - 1),
|
||||
leadingSection: sections[i - 1],
|
||||
trailingItem: getItem(sectionData, itemIndex + 1),
|
||||
trailingSection: sections[i + 1],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_convertViewable = (viewable: ViewToken): ?ViewToken => {
|
||||
invariant(viewable.index != null, 'Received a broken ViewToken');
|
||||
const info = this._subExtractor(viewable.index);
|
||||
if (!info) {
|
||||
return null;
|
||||
}
|
||||
const keyExtractor = info.section.keyExtractor || this.props.keyExtractor;
|
||||
return {
|
||||
...viewable,
|
||||
index: info.index,
|
||||
/* $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. */
|
||||
key: keyExtractor(viewable.item, info.index),
|
||||
section: info.section,
|
||||
};
|
||||
};
|
||||
|
||||
_onViewableItemsChanged = ({
|
||||
viewableItems,
|
||||
changed,
|
||||
}: {
|
||||
viewableItems: Array<ViewToken>,
|
||||
changed: Array<ViewToken>,
|
||||
...
|
||||
}) => {
|
||||
const onViewableItemsChanged = this.props.onViewableItemsChanged;
|
||||
if (onViewableItemsChanged != null) {
|
||||
onViewableItemsChanged({
|
||||
viewableItems: viewableItems
|
||||
.map(this._convertViewable, this)
|
||||
.filter(Boolean),
|
||||
changed: changed.map(this._convertViewable, this).filter(Boolean),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
_renderItem = ({item, index}: {item: Item, index: number, ...}) => {
|
||||
const info = this._subExtractor(index);
|
||||
if (!info) {
|
||||
return null;
|
||||
}
|
||||
const infoIndex = info.index;
|
||||
if (infoIndex == null) {
|
||||
const {section} = info;
|
||||
if (info.header === true) {
|
||||
const {renderSectionHeader} = this.props;
|
||||
return renderSectionHeader ? renderSectionHeader({section}) : null;
|
||||
} else {
|
||||
const {renderSectionFooter} = this.props;
|
||||
return renderSectionFooter ? renderSectionFooter({section}) : null;
|
||||
}
|
||||
} else {
|
||||
const renderItem = info.section.renderItem || this.props.renderItem;
|
||||
const SeparatorComponent = this._getSeparatorComponent(index, info);
|
||||
invariant(renderItem, 'no renderItem!');
|
||||
return (
|
||||
<ItemWithSeparator
|
||||
SeparatorComponent={SeparatorComponent}
|
||||
LeadingSeparatorComponent={
|
||||
infoIndex === 0 ? this.props.SectionSeparatorComponent : undefined
|
||||
}
|
||||
cellKey={info.key}
|
||||
index={infoIndex}
|
||||
item={item}
|
||||
leadingItem={info.leadingItem}
|
||||
leadingSection={info.leadingSection}
|
||||
onUpdateSeparator={this._onUpdateSeparator}
|
||||
prevCellKey={(this._subExtractor(index - 1) || {}).key}
|
||||
ref={ref => {
|
||||
this._cellRefs[info.key] = ref;
|
||||
}}
|
||||
renderItem={renderItem}
|
||||
section={info.section}
|
||||
trailingItem={info.trailingItem}
|
||||
trailingSection={info.trailingSection}
|
||||
inverted={!!this.props.inverted}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
_onUpdateSeparator = (key: string, newProps: Object) => {
|
||||
const ref = this._cellRefs[key];
|
||||
ref && ref.updateSeparatorProps(newProps);
|
||||
};
|
||||
|
||||
_getSeparatorComponent(
|
||||
index: number,
|
||||
info?: ?Object,
|
||||
): ?React.ComponentType<any> {
|
||||
info = info || this._subExtractor(index);
|
||||
if (!info) {
|
||||
return null;
|
||||
}
|
||||
const ItemSeparatorComponent =
|
||||
info.section.ItemSeparatorComponent || this.props.ItemSeparatorComponent;
|
||||
const {SectionSeparatorComponent} = this.props;
|
||||
const isLastItemInList = index === this.state.childProps.getItemCount() - 1;
|
||||
const isLastItemInSection =
|
||||
info.index === this.props.getItemCount(info.section.data) - 1;
|
||||
if (SectionSeparatorComponent && isLastItemInSection) {
|
||||
return SectionSeparatorComponent;
|
||||
}
|
||||
if (ItemSeparatorComponent && !isLastItemInSection && !isLastItemInList) {
|
||||
return ItemSeparatorComponent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_cellRefs = {};
|
||||
_listRef: VirtualizedList;
|
||||
_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._listRef = ref;
|
||||
};
|
||||
}
|
||||
|
||||
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,
|
||||
inverted: boolean,
|
||||
|}>;
|
||||
|
||||
type ItemWithSeparatorState = {
|
||||
separatorProps: $ReadOnly<{|
|
||||
highlighted: false,
|
||||
...ItemWithSeparatorCommonProps,
|
||||
|}>,
|
||||
leadingSeparatorProps: $ReadOnly<{|
|
||||
highlighted: false,
|
||||
...ItemWithSeparatorCommonProps,
|
||||
|}>,
|
||||
...
|
||||
};
|
||||
|
||||
class ItemWithSeparator extends React.Component<
|
||||
ItemWithSeparatorProps,
|
||||
ItemWithSeparatorState,
|
||||
> {
|
||||
state = {
|
||||
separatorProps: {
|
||||
highlighted: false,
|
||||
leadingItem: this.props.item,
|
||||
leadingSection: this.props.leadingSection,
|
||||
section: this.props.section,
|
||||
trailingItem: this.props.trailingItem,
|
||||
trailingSection: this.props.trailingSection,
|
||||
},
|
||||
leadingSeparatorProps: {
|
||||
highlighted: false,
|
||||
leadingItem: this.props.leadingItem,
|
||||
leadingSection: this.props.leadingSection,
|
||||
section: this.props.section,
|
||||
trailingItem: this.props.item,
|
||||
trailingSection: this.props.trailingSection,
|
||||
},
|
||||
};
|
||||
|
||||
_separators = {
|
||||
highlight: () => {
|
||||
['leading', 'trailing'].forEach(s =>
|
||||
this._separators.updateProps(s, {highlighted: true}),
|
||||
);
|
||||
},
|
||||
unhighlight: () => {
|
||||
['leading', 'trailing'].forEach(s =>
|
||||
this._separators.updateProps(s, {highlighted: false}),
|
||||
);
|
||||
},
|
||||
updateProps: (select: 'leading' | 'trailing', newProps: Object) => {
|
||||
const {LeadingSeparatorComponent, cellKey, prevCellKey} = this.props;
|
||||
if (select === 'leading' && LeadingSeparatorComponent != null) {
|
||||
this.setState(state => ({
|
||||
leadingSeparatorProps: {...state.leadingSeparatorProps, ...newProps},
|
||||
}));
|
||||
} else {
|
||||
this.props.onUpdateSeparator(
|
||||
(select === 'leading' && prevCellKey) || cellKey,
|
||||
newProps,
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(
|
||||
props: ItemWithSeparatorProps,
|
||||
prevState: ItemWithSeparatorState,
|
||||
): ?ItemWithSeparatorState {
|
||||
return {
|
||||
separatorProps: {
|
||||
...prevState.separatorProps,
|
||||
leadingItem: props.item,
|
||||
leadingSection: props.leadingSection,
|
||||
section: props.section,
|
||||
trailingItem: props.trailingItem,
|
||||
trailingSection: props.trailingSection,
|
||||
},
|
||||
leadingSeparatorProps: {
|
||||
...prevState.leadingSeparatorProps,
|
||||
leadingItem: props.leadingItem,
|
||||
leadingSection: props.leadingSection,
|
||||
section: props.section,
|
||||
trailingItem: props.item,
|
||||
trailingSection: props.trailingSection,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
updateSeparatorProps(newProps: Object) {
|
||||
this.setState(state => ({
|
||||
separatorProps: {...state.separatorProps, ...newProps},
|
||||
}));
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
LeadingSeparatorComponent,
|
||||
SeparatorComponent,
|
||||
item,
|
||||
index,
|
||||
section,
|
||||
inverted,
|
||||
} = this.props;
|
||||
const element = this.props.renderItem({
|
||||
item,
|
||||
index,
|
||||
section,
|
||||
separators: this._separators,
|
||||
});
|
||||
const leadingSeparator = LeadingSeparatorComponent && (
|
||||
<LeadingSeparatorComponent {...this.state.leadingSeparatorProps} />
|
||||
);
|
||||
const separator = SeparatorComponent && (
|
||||
<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>
|
||||
{!inverted ? leadingSeparator : separator}
|
||||
{element}
|
||||
{!inverted ? separator : leadingSeparator}
|
||||
</View>
|
||||
) : (
|
||||
element
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = VirtualizedSectionList;
|
117
node_modules/react-native/Libraries/Lists/__flowtests__/FlatList-flowtest.js
generated
vendored
Normal file
117
node_modules/react-native/Libraries/Lists/__flowtests__/FlatList-flowtest.js
generated
vendored
Normal file
@ -0,0 +1,117 @@
|
||||
/**
|
||||
* 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-local
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const FlatList = require('../FlatList');
|
||||
const React = require('react');
|
||||
|
||||
function renderMyListItem(info: {
|
||||
item: {title: string, ...},
|
||||
index: number,
|
||||
...
|
||||
}) {
|
||||
return <span />;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
testEverythingIsFine(): React.Node {
|
||||
const data = [
|
||||
{
|
||||
title: 'Title Text',
|
||||
key: 1,
|
||||
},
|
||||
];
|
||||
return <FlatList renderItem={renderMyListItem} data={data} />;
|
||||
},
|
||||
|
||||
testBadDataWithTypicalItem(): React.Node {
|
||||
const data = [
|
||||
{
|
||||
title: 6,
|
||||
key: 1,
|
||||
},
|
||||
];
|
||||
// $FlowExpectedError - bad title type 6, should be string
|
||||
return <FlatList renderItem={renderMyListItem} data={data} />;
|
||||
},
|
||||
|
||||
testMissingFieldWithTypicalItem(): React.Node {
|
||||
const data = [
|
||||
{
|
||||
key: 1,
|
||||
},
|
||||
];
|
||||
// $FlowExpectedError - missing title
|
||||
return <FlatList renderItem={renderMyListItem} data={data} />;
|
||||
},
|
||||
|
||||
testGoodDataWithBadCustomRenderItemFunction(): React.Node {
|
||||
const data = [
|
||||
{
|
||||
widget: 6,
|
||||
key: 1,
|
||||
},
|
||||
];
|
||||
return (
|
||||
<FlatList
|
||||
renderItem={info => (
|
||||
<span>
|
||||
{
|
||||
// $FlowExpectedError - bad widgetCount type 6, should be Object
|
||||
info.item.widget.missingProp
|
||||
}
|
||||
</span>
|
||||
)}
|
||||
data={data}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
testBadRenderItemFunction(): $TEMPORARY$array<React.Node> {
|
||||
const data = [
|
||||
{
|
||||
title: 'foo',
|
||||
key: 1,
|
||||
},
|
||||
];
|
||||
return [
|
||||
// $FlowExpectedError - title should be inside `item`
|
||||
<FlatList renderItem={(info: {title: string}) => <span />} data={data} />,
|
||||
<FlatList
|
||||
// $FlowExpectedError - bad index type string, should be number
|
||||
renderItem={(info: {item: any, index: string}) => <span />}
|
||||
data={data}
|
||||
/>,
|
||||
<FlatList
|
||||
// $FlowExpectedError - bad title type number, should be string
|
||||
renderItem={(info: {item: {title: number}}) => <span />}
|
||||
data={data}
|
||||
/>,
|
||||
// EverythingIsFine
|
||||
<FlatList
|
||||
// $FlowExpectedError - bad title type number, should be string
|
||||
renderItem={(info: {item: {title: string, ...}, ...}) => <span />}
|
||||
data={data}
|
||||
/>,
|
||||
];
|
||||
},
|
||||
|
||||
testOtherBadProps(): $TEMPORARY$array<React.Node> {
|
||||
return [
|
||||
// $FlowExpectedError - bad numColumns type "lots"
|
||||
<FlatList renderItem={renderMyListItem} data={[]} numColumns="lots" />,
|
||||
// $FlowExpectedError - bad windowSize type "big"
|
||||
<FlatList renderItem={renderMyListItem} data={[]} windowSize="big" />,
|
||||
// $FlowExpectedError - missing `data` prop
|
||||
<FlatList renderItem={renderMyListItem} />,
|
||||
];
|
||||
},
|
||||
};
|
134
node_modules/react-native/Libraries/Lists/__flowtests__/SectionList-flowtest.js
generated
vendored
Normal file
134
node_modules/react-native/Libraries/Lists/__flowtests__/SectionList-flowtest.js
generated
vendored
Normal file
@ -0,0 +1,134 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
const React = require('react');
|
||||
const SectionList = require('../SectionList');
|
||||
|
||||
function renderMyListItem(info: {
|
||||
item: {title: string, ...},
|
||||
index: number,
|
||||
...
|
||||
}) {
|
||||
return <span />;
|
||||
}
|
||||
|
||||
const renderMyHeader = ({
|
||||
section,
|
||||
}: {
|
||||
section: {fooNumber: number, ...} & Object,
|
||||
...
|
||||
}) => <span />;
|
||||
|
||||
module.exports = {
|
||||
testGoodDataWithGoodItem(): React.Node {
|
||||
const sections = [
|
||||
{
|
||||
key: 'a',
|
||||
data: [
|
||||
{
|
||||
title: 'foo',
|
||||
key: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
return <SectionList renderItem={renderMyListItem} sections={sections} />;
|
||||
},
|
||||
|
||||
testBadRenderItemFunction(): $TEMPORARY$array<React.Node> {
|
||||
const sections = [
|
||||
{
|
||||
key: 'a',
|
||||
data: [
|
||||
{
|
||||
title: 'foo',
|
||||
key: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
return [
|
||||
<SectionList
|
||||
// $FlowExpectedError - title should be inside `item`
|
||||
renderItem={(info: {title: string, ...}) => <span />}
|
||||
sections={sections}
|
||||
/>,
|
||||
<SectionList
|
||||
// $FlowExpectedError - bad index type string, should be number
|
||||
renderItem={(info: {index: string}) => <span />}
|
||||
sections={sections}
|
||||
/>,
|
||||
// EverythingIsFine
|
||||
<SectionList
|
||||
renderItem={(info: {item: {title: string, ...}, ...}) => <span />}
|
||||
sections={sections}
|
||||
/>,
|
||||
];
|
||||
},
|
||||
|
||||
testBadInheritedDefaultProp(): React.Element<*> {
|
||||
const sections = [];
|
||||
return (
|
||||
<SectionList
|
||||
renderItem={renderMyListItem}
|
||||
sections={sections}
|
||||
// $FlowExpectedError - bad windowSize type "big"
|
||||
windowSize="big"
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
testMissingData(): React.Element<*> {
|
||||
// $FlowExpectedError - missing `sections` prop
|
||||
return <SectionList renderItem={renderMyListItem} />;
|
||||
},
|
||||
|
||||
testBadSectionsShape(): React.Element<*> {
|
||||
const sections = [
|
||||
{
|
||||
key: 'a',
|
||||
items: [
|
||||
{
|
||||
title: 'foo',
|
||||
key: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
// $FlowExpectedError - section missing `data` field
|
||||
return <SectionList renderItem={renderMyListItem} sections={sections} />;
|
||||
},
|
||||
|
||||
testBadSectionsMetadata(): React.Element<*> {
|
||||
const sections = [
|
||||
{
|
||||
key: 'a',
|
||||
fooNumber: 'string',
|
||||
data: [
|
||||
{
|
||||
title: 'foo',
|
||||
key: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
return (
|
||||
<SectionList
|
||||
renderSectionHeader={renderMyHeader}
|
||||
renderItem={renderMyListItem}
|
||||
/* $FlowExpectedError - section has bad meta data `fooNumber` field of
|
||||
* type string */
|
||||
sections={sections}
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
Reference in New Issue
Block a user