mirror of
https://github.com/zoriya/react-native-web.git
synced 2025-12-06 06:36:13 +00:00
[fix] VirtualizedList sync from react-native
Fix #2432 Close #2167 Close #2502
This commit is contained in:
committed by
Nicolas Gallagher
parent
1c5119b7e1
commit
5ace60eb7e
@@ -6,11 +6,14 @@
|
||||
<PROJECT_ROOT>/packages/.*/dist/.*
|
||||
<PROJECT_ROOT>/packages/react-native-web-docs/.*
|
||||
<PROJECT_ROOT>/packages/react-native-web-examples/.*
|
||||
.*/node_modules/.*/.*.json
|
||||
|
||||
[include]
|
||||
|
||||
[declarations]
|
||||
.*/node_modules/.*
|
||||
|
||||
[libs]
|
||||
|
||||
[options]
|
||||
indexed_access=true
|
||||
munge_underscores=true
|
||||
|
||||
24
package-lock.json
generated
24
package-lock.json
generated
@@ -11179,6 +11179,11 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/memoize-one": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
|
||||
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
|
||||
},
|
||||
"node_modules/memorystream": {
|
||||
"version": "0.3.1",
|
||||
"dev": true,
|
||||
@@ -11736,6 +11741,11 @@
|
||||
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/nullthrows": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz",
|
||||
"integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw=="
|
||||
},
|
||||
"node_modules/nunjucks": {
|
||||
"version": "3.2.3",
|
||||
"dev": true,
|
||||
@@ -15472,7 +15482,9 @@
|
||||
"@babel/runtime": "^7.18.6",
|
||||
"fbjs": "^3.0.4",
|
||||
"inline-style-prefixer": "^6.0.1",
|
||||
"memoize-one": "^6.0.0",
|
||||
"normalize-css-color": "^1.0.2",
|
||||
"nullthrows": "^1.1.1",
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
"styleq": "^0.1.2"
|
||||
},
|
||||
@@ -22680,6 +22692,11 @@
|
||||
"version": "1.0.1",
|
||||
"dev": true
|
||||
},
|
||||
"memoize-one": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
|
||||
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
|
||||
},
|
||||
"memorystream": {
|
||||
"version": "0.3.1",
|
||||
"dev": true
|
||||
@@ -23016,6 +23033,11 @@
|
||||
"boolbase": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"nullthrows": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz",
|
||||
"integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw=="
|
||||
},
|
||||
"nunjucks": {
|
||||
"version": "3.2.3",
|
||||
"dev": true,
|
||||
@@ -23607,7 +23629,9 @@
|
||||
"@babel/runtime": "^7.18.6",
|
||||
"fbjs": "^3.0.4",
|
||||
"inline-style-prefixer": "^6.0.1",
|
||||
"memoize-one": "^6.0.0",
|
||||
"normalize-css-color": "^1.0.2",
|
||||
"nullthrows": "^1.1.1",
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
"styleq": "^0.1.2"
|
||||
}
|
||||
|
||||
@@ -25,7 +25,9 @@
|
||||
"@babel/runtime": "^7.18.6",
|
||||
"fbjs": "^3.0.4",
|
||||
"inline-style-prefixer": "^6.0.1",
|
||||
"memoize-one": "^6.0.0",
|
||||
"normalize-css-color": "^1.0.2",
|
||||
"nullthrows": "^1.1.1",
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
"styleq": "^0.1.2"
|
||||
},
|
||||
|
||||
@@ -37,7 +37,7 @@ import InteractionManager from '../../../exports/InteractionManager';
|
||||
class Batchinator {
|
||||
_callback: () => void;
|
||||
_delay: number;
|
||||
_taskHandle: ?{cancel: () => void};
|
||||
_taskHandle: ?{cancel: () => void, ...};
|
||||
constructor(callback: () => void, delayMS: number) {
|
||||
this._delay = delayMS;
|
||||
this._callback = callback;
|
||||
@@ -48,7 +48,7 @@ class Batchinator {
|
||||
* By default, if there is a pending task the callback is run immediately. Set the option abort to
|
||||
* true to not call the callback if it was pending.
|
||||
*/
|
||||
dispose(options: {abort: boolean} = {abort: false}) {
|
||||
dispose(options: {abort: boolean, ...} = {abort: false}) {
|
||||
if (this._taskHandle) {
|
||||
this._taskHandle.cancel();
|
||||
if (!options.abort) {
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {FrameMetricProps} from '../VirtualizedList/VirtualizedListProps';
|
||||
|
||||
export type FillRateInfo = Info;
|
||||
|
||||
class Info {
|
||||
@@ -47,16 +49,17 @@ let _sampleRate = DEBUG ? 1 : null;
|
||||
* `SceneTracker.getActiveScene` to determine the context of the events.
|
||||
*/
|
||||
class FillRateHelper {
|
||||
_anyBlankStartTime = (null: ?number);
|
||||
_anyBlankStartTime: ?number = null;
|
||||
_enabled = false;
|
||||
_getFrameMetrics: (index: number) => ?FrameMetrics;
|
||||
_info = new Info();
|
||||
_mostlyBlankStartTime = (null: ?number);
|
||||
_samplesStartTime = (null: ?number);
|
||||
_getFrameMetrics: (index: number, props: FrameMetricProps) => ?FrameMetrics;
|
||||
_info: Info = new Info();
|
||||
_mostlyBlankStartTime: ?number = null;
|
||||
_samplesStartTime: ?number = null;
|
||||
|
||||
static addListener(
|
||||
callback: FillRateInfo => void,
|
||||
): {remove: () => void, ...} {
|
||||
static addListener(callback: FillRateInfo => void): {
|
||||
remove: () => void,
|
||||
...
|
||||
} {
|
||||
if (_sampleRate === null) {
|
||||
console.warn('Call `FillRateHelper.setSampleRate` before `addListener`.');
|
||||
}
|
||||
@@ -76,7 +79,9 @@ class FillRateHelper {
|
||||
_minSampleCount = minSampleCount;
|
||||
}
|
||||
|
||||
constructor(getFrameMetrics: (index: number) => ?FrameMetrics) {
|
||||
constructor(
|
||||
getFrameMetrics: (index: number, props: FrameMetricProps) => ?FrameMetrics,
|
||||
) {
|
||||
this._getFrameMetrics = getFrameMetrics;
|
||||
this._enabled = (_sampleRate || 0) > Math.random();
|
||||
this._resetData();
|
||||
@@ -123,6 +128,7 @@ class FillRateHelper {
|
||||
mostly_blank_time_frac: this._info.mostly_blank_ms / total_time_spent,
|
||||
};
|
||||
for (const key in derived) {
|
||||
// $FlowFixMe[prop-missing]
|
||||
derived[key] = Math.round(1000 * derived[key]) / 1000;
|
||||
}
|
||||
console.debug('FillRateHelper deactivateAndFlush: ', {derived, info});
|
||||
@@ -133,12 +139,11 @@ class FillRateHelper {
|
||||
|
||||
computeBlankness(
|
||||
props: {
|
||||
data: any,
|
||||
getItemCount: (data: any) => number,
|
||||
initialNumToRender: number,
|
||||
...FrameMetricProps,
|
||||
initialNumToRender?: ?number,
|
||||
...
|
||||
},
|
||||
state: {
|
||||
cellsAroundViewport: {
|
||||
first: number,
|
||||
last: number,
|
||||
...
|
||||
@@ -154,6 +159,7 @@ class FillRateHelper {
|
||||
if (
|
||||
!this._enabled ||
|
||||
props.getItemCount(props.data) === 0 ||
|
||||
cellsAroundViewport.last < cellsAroundViewport.first ||
|
||||
this._samplesStartTime == null
|
||||
) {
|
||||
return 0;
|
||||
@@ -179,10 +185,13 @@ class FillRateHelper {
|
||||
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);
|
||||
let first = cellsAroundViewport.first;
|
||||
let firstFrame = this._getFrameMetrics(first, props);
|
||||
while (
|
||||
first <= cellsAroundViewport.last &&
|
||||
(!firstFrame || !firstFrame.inLayout)
|
||||
) {
|
||||
firstFrame = this._getFrameMetrics(first, props);
|
||||
first++;
|
||||
}
|
||||
// Only count blankTop if we aren't rendering the first item, otherwise we will count the header
|
||||
@@ -194,10 +203,13 @@ class FillRateHelper {
|
||||
);
|
||||
}
|
||||
let blankBottom = 0;
|
||||
let last = state.last;
|
||||
let lastFrame = this._getFrameMetrics(last);
|
||||
while (last >= state.first && (!lastFrame || !lastFrame.inLayout)) {
|
||||
lastFrame = this._getFrameMetrics(last);
|
||||
let last = cellsAroundViewport.last;
|
||||
let lastFrame = this._getFrameMetrics(last, props);
|
||||
while (
|
||||
last >= cellsAroundViewport.first &&
|
||||
(!lastFrame || !lastFrame.inLayout)
|
||||
) {
|
||||
lastFrame = this._getFrameMetrics(last, props);
|
||||
last--;
|
||||
}
|
||||
// Only count blankBottom if we aren't rendering the last item, otherwise we will count the
|
||||
|
||||
@@ -8,33 +8,34 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import Platform from '../../../exports/Platform';
|
||||
import deepDiffer from '../deepDiffer';
|
||||
import * as React from 'react';
|
||||
import View, { type ViewProps } from '../../../exports/View';
|
||||
import VirtualizedList from '../VirtualizedList';
|
||||
import StyleSheet from '../../../exports/StyleSheet';
|
||||
|
||||
import deepDiffer from '../deepDiffer';
|
||||
import Platform from '../../../exports/Platform';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import * as React from 'react';
|
||||
|
||||
type ScrollViewNativeComponent = any;
|
||||
type ScrollResponderType = any;
|
||||
type ViewStyleProp = $PropertyType<ViewProps, 'style'>;
|
||||
import type {
|
||||
ViewToken,
|
||||
ViewabilityConfigCallbackPair,
|
||||
} from '../ViewabilityHelper';
|
||||
import type {RenderItemType, RenderItemProps} from '../VirtualizedList';
|
||||
type ScrollResponderType = any;
|
||||
import VirtualizedList from '../VirtualizedList';
|
||||
import {keyExtractor as defaultKeyExtractor} from '../VirtualizeUtils';
|
||||
|
||||
import memoizeOne from 'memoize-one';
|
||||
|
||||
type $FlowFixMe = any;
|
||||
|
||||
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.
|
||||
* An array (or array-like list) of items to render. Other data types can be
|
||||
* used by targetting VirtualizedList directly.
|
||||
*/
|
||||
data: ?$ReadOnlyArray<ItemT>,
|
||||
data: ?$ArrayLike<ItemT>,
|
||||
|};
|
||||
type OptionalProps<ItemT> = {|
|
||||
/**
|
||||
@@ -89,7 +90,7 @@ type OptionalProps<ItemT> = {|
|
||||
* specify `ItemSeparatorComponent`.
|
||||
*/
|
||||
getItemLayout?: (
|
||||
data: ?Array<ItemT>,
|
||||
data: ?$ArrayLike<ItemT>,
|
||||
index: number,
|
||||
) => {
|
||||
length: number,
|
||||
@@ -143,6 +144,10 @@ type OptionalProps<ItemT> = {|
|
||||
* See `ScrollView` for flow type and further documentation.
|
||||
*/
|
||||
fadingEdgeLength?: ?number,
|
||||
/**
|
||||
* Enable an optimization to memoize the item renderer to prevent unnecessary rerenders.
|
||||
*/
|
||||
strictMode?: boolean,
|
||||
|};
|
||||
|
||||
/**
|
||||
@@ -160,6 +165,11 @@ function numColumnsOrDefault(numColumns: ?number) {
|
||||
return numColumns ?? 1;
|
||||
}
|
||||
|
||||
function isArrayLike(data: mixed): boolean {
|
||||
// $FlowExpectedError[incompatible-use]
|
||||
return typeof Object(data).length === 'number';
|
||||
}
|
||||
|
||||
type FlatListProps<ItemT> = {|
|
||||
...RequiredProps<ItemT>,
|
||||
...OptionalProps<ItemT>,
|
||||
@@ -331,6 +341,7 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
|
||||
scrollToItem(params: {
|
||||
animated?: ?boolean,
|
||||
item: ItemT,
|
||||
viewOffset?: number,
|
||||
viewPosition?: number,
|
||||
...
|
||||
}) {
|
||||
@@ -424,6 +435,7 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
|
||||
}
|
||||
}
|
||||
|
||||
// $FlowFixMe[missing-local-annot]
|
||||
componentDidUpdate(prevProps: Props<ItemT>) {
|
||||
invariant(
|
||||
prevProps.numColumns === this.props.numColumns,
|
||||
@@ -450,10 +462,11 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
|
||||
_listRef: ?React.ElementRef<typeof VirtualizedList>;
|
||||
_virtualizedListPairs: Array<ViewabilityConfigCallbackPair> = [];
|
||||
|
||||
_captureRef = ref => {
|
||||
_captureRef = (ref: ?React.ElementRef<typeof VirtualizedList>) => {
|
||||
this._listRef = ref;
|
||||
};
|
||||
|
||||
// $FlowFixMe[missing-local-annot]
|
||||
_checkProps(props: Props<ItemT>) {
|
||||
const {
|
||||
// $FlowFixMe[prop-missing] this prop doesn't exist, is only used for an invariant
|
||||
@@ -485,13 +498,17 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
|
||||
);
|
||||
}
|
||||
|
||||
_getItem = (data: Array<ItemT>, index: number) => {
|
||||
_getItem = (
|
||||
data: $ArrayLike<ItemT>,
|
||||
index: number,
|
||||
): ?(ItemT | $ReadOnlyArray<ItemT>) => {
|
||||
const numColumns = numColumnsOrDefault(this.props.numColumns);
|
||||
if (numColumns > 1) {
|
||||
const ret = [];
|
||||
for (let kk = 0; kk < numColumns; kk++) {
|
||||
const item = data[index * numColumns + kk];
|
||||
if (item != null) {
|
||||
const itemIndex = index * numColumns + kk;
|
||||
if (itemIndex < data.length) {
|
||||
const item = data[itemIndex];
|
||||
ret.push(item);
|
||||
}
|
||||
}
|
||||
@@ -501,8 +518,14 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
|
||||
}
|
||||
};
|
||||
|
||||
_getItemCount = (data: ?Array<ItemT>): number => {
|
||||
if (data) {
|
||||
_getItemCount = (data: ?$ArrayLike<ItemT>): number => {
|
||||
// Legacy behavior of FlatList was to forward "undefined" length if invalid
|
||||
// data like a non-arraylike object is passed. VirtualizedList would then
|
||||
// coerce this, and the math would work out to no-op. For compatibility, if
|
||||
// invalid data is passed, we tell VirtualizedList there are zero items
|
||||
// available to prevent it from trying to read from the invalid data
|
||||
// (without propagating invalidly typed data).
|
||||
if (data != null && isArrayLike(data)) {
|
||||
const numColumns = numColumnsOrDefault(this.props.numColumns);
|
||||
return numColumns > 1 ? Math.ceil(data.length / numColumns) : data.length;
|
||||
} else {
|
||||
@@ -510,29 +533,26 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
|
||||
}
|
||||
};
|
||||
|
||||
_keyExtractor = (items: ItemT | Array<ItemT>, index: number) => {
|
||||
_keyExtractor = (items: ItemT | Array<ItemT>, index: number): string => {
|
||||
const numColumns = numColumnsOrDefault(this.props.numColumns);
|
||||
const keyExtractor = this.props.keyExtractor ?? defaultKeyExtractor;
|
||||
|
||||
if (numColumns > 1) {
|
||||
if (Array.isArray(items)) {
|
||||
return items
|
||||
.map((item, kk) =>
|
||||
keyExtractor(((item: $FlowFixMe): ItemT), index * numColumns + kk),
|
||||
)
|
||||
.join(':');
|
||||
} else {
|
||||
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,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// $FlowFixMe[incompatible-call] Can't call keyExtractor with an array
|
||||
return keyExtractor(items, index);
|
||||
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((item, kk) =>
|
||||
keyExtractor(((item: $FlowFixMe): ItemT), index * numColumns + kk),
|
||||
)
|
||||
.join(':');
|
||||
}
|
||||
|
||||
// $FlowFixMe[incompatible-call] Can't call keyExtractor with an array
|
||||
return keyExtractor(items, index);
|
||||
};
|
||||
|
||||
_pushMultiColumnViewable(arr: Array<ViewToken>, v: ViewToken): void {
|
||||
@@ -551,6 +571,7 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
|
||||
changed: Array<ViewToken>,
|
||||
...
|
||||
}) => void,
|
||||
// $FlowFixMe[missing-local-annot]
|
||||
) {
|
||||
return (info: {
|
||||
viewableItems: Array<ViewToken>,
|
||||
@@ -560,8 +581,8 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
|
||||
const numColumns = numColumnsOrDefault(this.props.numColumns);
|
||||
if (onViewableItemsChanged) {
|
||||
if (numColumns > 1) {
|
||||
const changed = [];
|
||||
const viewableItems = [];
|
||||
const changed: Array<ViewToken> = [];
|
||||
const viewableItems: Array<ViewToken> = [];
|
||||
info.viewableItems.forEach(v =>
|
||||
this._pushMultiColumnViewable(viewableItems, v),
|
||||
);
|
||||
@@ -574,15 +595,17 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
|
||||
};
|
||||
}
|
||||
|
||||
_renderer = () => {
|
||||
const {ListItemComponent, renderItem, columnWrapperStyle} = this.props;
|
||||
const numColumns = numColumnsOrDefault(this.props.numColumns);
|
||||
_renderer = (
|
||||
ListItemComponent: ?(React.ComponentType<any> | React.Element<any>),
|
||||
renderItem: ?RenderItemType<ItemT>,
|
||||
columnWrapperStyle: ?ViewStyleProp,
|
||||
numColumns: ?number,
|
||||
extraData: ?any,
|
||||
// $FlowFixMe[missing-local-annot]
|
||||
) => {
|
||||
const cols = numColumnsOrDefault(numColumns);
|
||||
|
||||
let virtualizedListRenderKey = ListItemComponent
|
||||
? 'ListItemComponent'
|
||||
: 'renderItem';
|
||||
|
||||
const renderer = (props): React.Node => {
|
||||
const render = (props: RenderItemProps<ItemT>): React.Node => {
|
||||
if (ListItemComponent) {
|
||||
// $FlowFixMe[not-a-component] Component isn't valid
|
||||
// $FlowFixMe[incompatible-type-arg] Component isn't valid
|
||||
@@ -596,47 +619,54 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
/* $FlowFixMe[invalid-computed-prop] (>=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={[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);
|
||||
}
|
||||
},
|
||||
const renderProp = (info: RenderItemProps<ItemT>) => {
|
||||
if (cols > 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 = render({
|
||||
// $FlowFixMe[incompatible-call]
|
||||
item: it,
|
||||
index: index * cols + kk,
|
||||
separators: info.separators,
|
||||
});
|
||||
return element != null ? (
|
||||
<React.Fragment key={kk}>{element}</React.Fragment>
|
||||
) : null;
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
return render(info);
|
||||
}
|
||||
};
|
||||
|
||||
return ListItemComponent
|
||||
? {ListItemComponent: renderProp}
|
||||
: {renderItem: renderProp};
|
||||
};
|
||||
|
||||
// $FlowFixMe[missing-local-annot]
|
||||
_memoizedRenderer = memoizeOne(this._renderer);
|
||||
|
||||
render(): React.Node {
|
||||
const {
|
||||
numColumns,
|
||||
columnWrapperStyle,
|
||||
removeClippedSubviews: _removeClippedSubviews,
|
||||
strictMode = false,
|
||||
...restProps
|
||||
} = this.props;
|
||||
|
||||
const renderer = strictMode ? this._memoizedRenderer : this._renderer;
|
||||
|
||||
return (
|
||||
// $FlowFixMe[incompatible-exact] - `restProps` (`Props`) is inexact.
|
||||
<VirtualizedList
|
||||
{...restProps}
|
||||
getItem={this._getItem}
|
||||
@@ -647,7 +677,13 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
|
||||
removeClippedSubviews={removeClippedSubviewsOrDefault(
|
||||
_removeClippedSubviews,
|
||||
)}
|
||||
{...this._renderer()}
|
||||
{...renderer(
|
||||
this.props.ListItemComponent,
|
||||
this.props.renderItem,
|
||||
columnWrapperStyle,
|
||||
numColumns,
|
||||
this.props.extraData,
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
export type SyntheticEvent<T> = $ReadOnly<{|
|
||||
export type SyntheticEvent<+T> = $ReadOnly<{|
|
||||
bubbles: ?boolean,
|
||||
cancelable: ?boolean,
|
||||
currentTarget: HTMLElement,
|
||||
@@ -82,6 +82,144 @@ export type TextLayoutEvent = SyntheticEvent<
|
||||
|}>,
|
||||
>;
|
||||
|
||||
/**
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/UIEvent
|
||||
*/
|
||||
export interface NativeUIEvent {
|
||||
/**
|
||||
* Returns a long with details about the event, depending on the event type.
|
||||
*/
|
||||
+detail: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent
|
||||
*/
|
||||
export interface NativeMouseEvent extends NativeUIEvent {
|
||||
/**
|
||||
* The X coordinate of the mouse pointer in global (screen) coordinates.
|
||||
*/
|
||||
+screenX: number;
|
||||
/**
|
||||
* The Y coordinate of the mouse pointer in global (screen) coordinates.
|
||||
*/
|
||||
+screenY: number;
|
||||
/**
|
||||
* The X coordinate of the mouse pointer relative to the whole document.
|
||||
*/
|
||||
+pageX: number;
|
||||
/**
|
||||
* The Y coordinate of the mouse pointer relative to the whole document.
|
||||
*/
|
||||
+pageY: number;
|
||||
/**
|
||||
* The X coordinate of the mouse pointer in local (DOM content) coordinates.
|
||||
*/
|
||||
+clientX: number;
|
||||
/**
|
||||
* The Y coordinate of the mouse pointer in local (DOM content) coordinates.
|
||||
*/
|
||||
+clientY: number;
|
||||
/**
|
||||
* Alias for NativeMouseEvent.clientX
|
||||
*/
|
||||
+x: number;
|
||||
/**
|
||||
* Alias for NativeMouseEvent.clientY
|
||||
*/
|
||||
+y: number;
|
||||
/**
|
||||
* Returns true if the control key was down when the mouse event was fired.
|
||||
*/
|
||||
+ctrlKey: boolean;
|
||||
/**
|
||||
* Returns true if the shift key was down when the mouse event was fired.
|
||||
*/
|
||||
+shiftKey: boolean;
|
||||
/**
|
||||
* Returns true if the alt key was down when the mouse event was fired.
|
||||
*/
|
||||
+altKey: boolean;
|
||||
/**
|
||||
* Returns true if the meta key was down when the mouse event was fired.
|
||||
*/
|
||||
+metaKey: boolean;
|
||||
/**
|
||||
* The button number that was pressed (if applicable) when the mouse event was fired.
|
||||
*/
|
||||
+button: number;
|
||||
/**
|
||||
* The buttons being depressed (if any) when the mouse event was fired.
|
||||
*/
|
||||
+buttons: number;
|
||||
/**
|
||||
* The secondary target for the event, if there is one.
|
||||
*/
|
||||
+relatedTarget: HTMLElement;
|
||||
// offset is proposed: https://drafts.csswg.org/cssom-view/#extensions-to-the-mouseevent-interface
|
||||
/**
|
||||
* The X coordinate of the mouse pointer between that event and the padding edge of the target node
|
||||
*/
|
||||
+offsetX: number;
|
||||
/**
|
||||
* The Y coordinate of the mouse pointer between that event and the padding edge of the target node
|
||||
*/
|
||||
+offsetY: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent
|
||||
*/
|
||||
export interface NativePointerEvent extends NativeMouseEvent {
|
||||
/**
|
||||
* A unique identifier for the pointer causing the event.
|
||||
*/
|
||||
+pointerId: number;
|
||||
/**
|
||||
* The width (magnitude on the X axis), in CSS pixels, of the contact geometry of the pointer
|
||||
*/
|
||||
+width: number;
|
||||
/**
|
||||
* The height (magnitude on the Y axis), in CSS pixels, of the contact geometry of the pointer.
|
||||
*/
|
||||
+height: number;
|
||||
/**
|
||||
* The normalized pressure of the pointer input in the range 0 to 1, where 0 and 1 represent
|
||||
* the minimum and maximum pressure the hardware is capable of detecting, respectively.
|
||||
*/
|
||||
+pressure: number;
|
||||
/**
|
||||
* The normalized tangential pressure of the pointer input (also known as barrel pressure or
|
||||
* cylinder stress) in the range -1 to 1, where 0 is the neutral position of the control.
|
||||
*/
|
||||
+tangentialPressure: number;
|
||||
/**
|
||||
* The plane angle (in degrees, in the range of -90 to 90) between the Y–Z plane and the plane
|
||||
* containing both the pointer (e.g. pen stylus) axis and the Y axis.
|
||||
*/
|
||||
+tiltX: number;
|
||||
/**
|
||||
* The plane angle (in degrees, in the range of -90 to 90) between the X–Z plane and the plane
|
||||
* containing both the pointer (e.g. pen stylus) axis and the X axis.
|
||||
*/
|
||||
+tiltY: number;
|
||||
/**
|
||||
* The clockwise rotation of the pointer (e.g. pen stylus) around its major axis in degrees,
|
||||
* with a value in the range 0 to 359.
|
||||
*/
|
||||
+twist: number;
|
||||
/**
|
||||
* Indicates the device type that caused the event (mouse, pen, touch, etc.)
|
||||
*/
|
||||
+pointerType: string;
|
||||
/**
|
||||
* Indicates if the pointer represents the primary pointer of this pointer type.
|
||||
*/
|
||||
+isPrimary: boolean;
|
||||
}
|
||||
|
||||
export type PointerEvent = SyntheticEvent<NativePointerEvent>;
|
||||
|
||||
export type PressEvent = ResponderSyntheticEvent<
|
||||
$ReadOnly<{|
|
||||
changedTouches: $ReadOnlyArray<$PropertyType<PressEvent, 'nativeEvent'>>,
|
||||
@@ -130,8 +268,24 @@ export type ScrollEvent = SyntheticEvent<
|
||||
|}>,
|
||||
>;
|
||||
|
||||
export type SwitchChangeEvent = SyntheticEvent<
|
||||
export type BlurEvent = SyntheticEvent<
|
||||
$ReadOnly<{|
|
||||
value: boolean,
|
||||
target: number,
|
||||
|}>,
|
||||
>;
|
||||
|
||||
export type FocusEvent = SyntheticEvent<
|
||||
$ReadOnly<{|
|
||||
target: number,
|
||||
|}>,
|
||||
>;
|
||||
|
||||
export type MouseEvent = SyntheticEvent<
|
||||
$ReadOnly<{|
|
||||
clientX: number,
|
||||
clientY: number,
|
||||
pageX: number,
|
||||
pageY: number,
|
||||
timestamp: number,
|
||||
|}>,
|
||||
>;
|
||||
|
||||
23
packages/react-native-web/src/vendor/react-native/Utilities/clamp.js
vendored
Normal file
23
packages/react-native-web/src/vendor/react-native/Utilities/clamp.js
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 strict
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
function clamp(min: number, value: number, max: number): number {
|
||||
if (value < min) {
|
||||
return min;
|
||||
}
|
||||
if (value > max) {
|
||||
return max;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
module.exports = clamp;
|
||||
@@ -7,8 +7,11 @@
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {FrameMetricProps} from '../VirtualizedList/VirtualizedListProps';
|
||||
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
export type ViewToken = {
|
||||
@@ -17,6 +20,7 @@ export type ViewToken = {
|
||||
index: ?number,
|
||||
isViewable: boolean,
|
||||
section?: any,
|
||||
...
|
||||
};
|
||||
|
||||
export type ViewabilityConfigCallbackPair = {
|
||||
@@ -24,7 +28,9 @@ export type ViewabilityConfigCallbackPair = {
|
||||
onViewableItemsChanged: (info: {
|
||||
viewableItems: Array<ViewToken>,
|
||||
changed: Array<ViewToken>,
|
||||
...
|
||||
}) => void,
|
||||
...
|
||||
};
|
||||
|
||||
export type ViewabilityConfig = {|
|
||||
@@ -71,7 +77,7 @@ export type ViewabilityConfig = {|
|
||||
class ViewabilityHelper {
|
||||
_config: ViewabilityConfig;
|
||||
_hasInteracted: boolean = false;
|
||||
_timers: Set<TimeoutID> = new Set();
|
||||
_timers: Set<number> = new Set();
|
||||
_viewableIndices: Array<number> = [];
|
||||
_viewableItems: Map<string, ViewToken> = new Map();
|
||||
|
||||
@@ -85,6 +91,9 @@ class ViewabilityHelper {
|
||||
* Cleanup, e.g. on unmount. Clears any pending timers.
|
||||
*/
|
||||
dispose() {
|
||||
/* $FlowFixMe[incompatible-call] (>=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);
|
||||
}
|
||||
|
||||
@@ -92,16 +101,27 @@ class ViewabilityHelper {
|
||||
* Determines which items are viewable based on the current metrics and config.
|
||||
*/
|
||||
computeViewableItems(
|
||||
itemCount: number,
|
||||
props: FrameMetricProps,
|
||||
scrollOffset: number,
|
||||
viewportHeight: number,
|
||||
getFrameMetrics: (index: number) => ?{length: number, offset: number},
|
||||
renderRange?: {first: number, last: number}, // Optional optimization to reduce the scan size
|
||||
getFrameMetrics: (
|
||||
index: number,
|
||||
props: FrameMetricProps,
|
||||
) => ?{
|
||||
length: number,
|
||||
offset: number,
|
||||
...
|
||||
},
|
||||
// Optional optimization to reduce the scan size
|
||||
renderRange?: {
|
||||
first: number,
|
||||
last: number,
|
||||
...
|
||||
},
|
||||
): Array<number> {
|
||||
const {
|
||||
itemVisiblePercentThreshold,
|
||||
viewAreaCoveragePercentThreshold,
|
||||
} = this._config;
|
||||
const itemCount = props.getItemCount(props.data);
|
||||
const {itemVisiblePercentThreshold, viewAreaCoveragePercentThreshold} =
|
||||
this._config;
|
||||
const viewAreaMode = viewAreaCoveragePercentThreshold != null;
|
||||
const viewablePercentThreshold = viewAreaMode
|
||||
? viewAreaCoveragePercentThreshold
|
||||
@@ -126,7 +146,7 @@ class ViewabilityHelper {
|
||||
return [];
|
||||
}
|
||||
for (let idx = first; idx <= last; idx++) {
|
||||
const metrics = getFrameMetrics(idx);
|
||||
const metrics = getFrameMetrics(idx, props);
|
||||
if (!metrics) {
|
||||
continue;
|
||||
}
|
||||
@@ -158,28 +178,46 @@ class ViewabilityHelper {
|
||||
* `onViewableItemsChanged` as appropriate.
|
||||
*/
|
||||
onUpdate(
|
||||
itemCount: number,
|
||||
props: FrameMetricProps,
|
||||
scrollOffset: number,
|
||||
viewportHeight: number,
|
||||
getFrameMetrics: (index: number) => ?{length: number, offset: number},
|
||||
createViewToken: (index: number, isViewable: boolean) => ViewToken,
|
||||
getFrameMetrics: (
|
||||
index: number,
|
||||
props: FrameMetricProps,
|
||||
) => ?{
|
||||
length: number,
|
||||
offset: number,
|
||||
...
|
||||
},
|
||||
createViewToken: (
|
||||
index: number,
|
||||
isViewable: boolean,
|
||||
props: FrameMetricProps,
|
||||
) => ViewToken,
|
||||
onViewableItemsChanged: ({
|
||||
viewableItems: Array<ViewToken>,
|
||||
changed: Array<ViewToken>,
|
||||
...
|
||||
}) => void,
|
||||
renderRange?: {first: number, last: number}, // Optional optimization to reduce the scan size
|
||||
// Optional optimization to reduce the scan size
|
||||
renderRange?: {
|
||||
first: number,
|
||||
last: number,
|
||||
...
|
||||
},
|
||||
): void {
|
||||
const itemCount = props.getItemCount(props.data);
|
||||
if (
|
||||
(this._config.waitForInteraction && !this._hasInteracted) ||
|
||||
itemCount === 0 ||
|
||||
!getFrameMetrics(0)
|
||||
!getFrameMetrics(0, props)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let viewableIndices = [];
|
||||
let viewableIndices: Array<number> = [];
|
||||
if (itemCount) {
|
||||
viewableIndices = this.computeViewableItems(
|
||||
itemCount,
|
||||
props,
|
||||
scrollOffset,
|
||||
viewportHeight,
|
||||
getFrameMetrics,
|
||||
@@ -196,17 +234,25 @@ class ViewabilityHelper {
|
||||
}
|
||||
this._viewableIndices = viewableIndices;
|
||||
if (this._config.minimumViewTime) {
|
||||
const handle = setTimeout(() => {
|
||||
const handle: TimeoutID = setTimeout(() => {
|
||||
/* $FlowFixMe[incompatible-call] (>=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(
|
||||
props,
|
||||
viewableIndices,
|
||||
onViewableItemsChanged,
|
||||
createViewToken,
|
||||
);
|
||||
}, this._config.minimumViewTime);
|
||||
/* $FlowFixMe[incompatible-call] (>=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(
|
||||
props,
|
||||
viewableIndices,
|
||||
onViewableItemsChanged,
|
||||
createViewToken,
|
||||
@@ -229,12 +275,18 @@ class ViewabilityHelper {
|
||||
}
|
||||
|
||||
_onUpdateSync(
|
||||
// $FlowFixMe
|
||||
viewableIndicesToCheck,
|
||||
// $FlowFixMe
|
||||
onViewableItemsChanged,
|
||||
// $FlowFixMe
|
||||
createViewToken,
|
||||
props: FrameMetricProps,
|
||||
viewableIndicesToCheck: Array<number>,
|
||||
onViewableItemsChanged: ({
|
||||
changed: Array<ViewToken>,
|
||||
viewableItems: Array<ViewToken>,
|
||||
...
|
||||
}) => void,
|
||||
createViewToken: (
|
||||
index: number,
|
||||
isViewable: boolean,
|
||||
props: FrameMetricProps,
|
||||
) => ViewToken,
|
||||
) {
|
||||
// Filter out indices that have gone out of view since this call was scheduled.
|
||||
viewableIndicesToCheck = viewableIndicesToCheck.filter(ii =>
|
||||
@@ -243,7 +295,7 @@ class ViewabilityHelper {
|
||||
const prevItems = this._viewableItems;
|
||||
const nextItems = new Map(
|
||||
viewableIndicesToCheck.map(ii => {
|
||||
const viewable = createViewToken(ii, true);
|
||||
const viewable = createViewToken(ii, true, props);
|
||||
return [viewable.key, viewable];
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import type {FrameMetricProps} from '../VirtualizedList/VirtualizedListProps';
|
||||
|
||||
/**
|
||||
* Used to find the indices of the frames that overlap the given offsets. Useful for finding the
|
||||
@@ -19,34 +19,48 @@ import invariant from 'fbjs/lib/invariant';
|
||||
*/
|
||||
export function elementsThatOverlapOffsets(
|
||||
offsets: Array<number>,
|
||||
itemCount: number,
|
||||
getFrameMetrics: (index: number) => {
|
||||
props: FrameMetricProps,
|
||||
getFrameMetrics: (
|
||||
index: number,
|
||||
props: FrameMetricProps,
|
||||
) => {
|
||||
length: number,
|
||||
offset: number,
|
||||
...
|
||||
},
|
||||
zoomScale: number = 1,
|
||||
): 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;
|
||||
}
|
||||
const itemCount = props.getItemCount(props.data);
|
||||
const result = [];
|
||||
for (let offsetIndex = 0; offsetIndex < offsets.length; offsetIndex++) {
|
||||
const currentOffset = offsets[offsetIndex];
|
||||
let left = 0;
|
||||
let right = itemCount - 1;
|
||||
|
||||
while (left <= right) {
|
||||
// eslint-disable-next-line no-bitwise
|
||||
const mid = left + ((right - left) >>> 1);
|
||||
const frame = getFrameMetrics(mid, props);
|
||||
const scaledOffsetStart = frame.offset * zoomScale;
|
||||
const scaledOffsetEnd = (frame.offset + frame.length) * zoomScale;
|
||||
|
||||
// We want the first frame that contains the offset, with inclusive bounds. Thus, for the
|
||||
// first frame the scaledOffsetStart is inclusive, while for other frames it is exclusive.
|
||||
if (
|
||||
(mid === 0 && currentOffset < scaledOffsetStart) ||
|
||||
(mid !== 0 && currentOffset <= scaledOffsetStart)
|
||||
) {
|
||||
right = mid - 1;
|
||||
} else if (currentOffset > scaledOffsetEnd) {
|
||||
left = mid + 1;
|
||||
} else {
|
||||
result[offsetIndex] = mid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,16 +99,17 @@ export function newRangeCount(
|
||||
* biased in the direction of scroll.
|
||||
*/
|
||||
export function computeWindowedRenderLimits(
|
||||
data: any,
|
||||
getItemCount: (data: any) => number,
|
||||
props: FrameMetricProps,
|
||||
maxToRenderPerBatch: number,
|
||||
windowSize: number,
|
||||
prev: {
|
||||
first: number,
|
||||
last: number,
|
||||
...
|
||||
},
|
||||
getFrameMetricsApprox: (index: number) => {
|
||||
getFrameMetricsApprox: (
|
||||
index: number,
|
||||
props: FrameMetricProps,
|
||||
) => {
|
||||
length: number,
|
||||
offset: number,
|
||||
...
|
||||
@@ -104,18 +119,18 @@ export function computeWindowedRenderLimits(
|
||||
offset: number,
|
||||
velocity: number,
|
||||
visibleLength: number,
|
||||
zoomScale: number,
|
||||
...
|
||||
},
|
||||
): {
|
||||
first: number,
|
||||
last: number,
|
||||
...
|
||||
} {
|
||||
const itemCount = getItemCount(data);
|
||||
const itemCount = props.getItemCount(props.data);
|
||||
if (itemCount === 0) {
|
||||
return prev;
|
||||
return {first: 0, last: -1};
|
||||
}
|
||||
const {offset, velocity, visibleLength} = scrollMetrics;
|
||||
const {offset, velocity, visibleLength, zoomScale = 1} = 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
|
||||
@@ -136,7 +151,8 @@ export function computeWindowedRenderLimits(
|
||||
);
|
||||
const overscanEnd = Math.max(0, visibleEnd + leadFactor * overscanLength);
|
||||
|
||||
const lastItemOffset = getFrameMetricsApprox(itemCount - 1).offset;
|
||||
const lastItemOffset =
|
||||
getFrameMetricsApprox(itemCount - 1, props).offset * zoomScale;
|
||||
if (lastItemOffset < overscanBegin) {
|
||||
// Entire list is before our overscan window
|
||||
return {
|
||||
@@ -148,8 +164,9 @@ export function computeWindowedRenderLimits(
|
||||
// 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],
|
||||
itemCount,
|
||||
props,
|
||||
getFrameMetricsApprox,
|
||||
zoomScale,
|
||||
);
|
||||
overscanFirst = overscanFirst == null ? 0 : overscanFirst;
|
||||
first = first == null ? Math.max(0, overscanFirst) : first;
|
||||
|
||||
155
packages/react-native-web/src/vendor/react-native/VirtualizedList/CellRenderMask.js
vendored
Normal file
155
packages/react-native-web/src/vendor/react-native/VirtualizedList/CellRenderMask.js
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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
|
||||
*/
|
||||
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
export type CellRegion = {
|
||||
first: number,
|
||||
last: number,
|
||||
isSpacer: boolean,
|
||||
};
|
||||
|
||||
export class CellRenderMask {
|
||||
_numCells: number;
|
||||
_regions: Array<CellRegion>;
|
||||
|
||||
constructor(numCells: number) {
|
||||
invariant(
|
||||
numCells >= 0,
|
||||
'CellRenderMask must contain a non-negative number os cells',
|
||||
);
|
||||
|
||||
this._numCells = numCells;
|
||||
|
||||
if (numCells === 0) {
|
||||
this._regions = [];
|
||||
} else {
|
||||
this._regions = [
|
||||
{
|
||||
first: 0,
|
||||
last: numCells - 1,
|
||||
isSpacer: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
enumerateRegions(): $ReadOnlyArray<CellRegion> {
|
||||
return this._regions;
|
||||
}
|
||||
|
||||
addCells(cells: {first: number, last: number}): void {
|
||||
invariant(
|
||||
cells.first >= 0 &&
|
||||
cells.first < this._numCells &&
|
||||
cells.last >= -1 &&
|
||||
cells.last < this._numCells &&
|
||||
cells.last >= cells.first - 1,
|
||||
'CellRenderMask.addCells called with invalid cell range',
|
||||
);
|
||||
|
||||
// VirtualizedList uses inclusive ranges, where zero-count states are
|
||||
// possible. E.g. [0, -1] for no cells, starting at 0.
|
||||
if (cells.last < cells.first) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [firstIntersect, firstIntersectIdx] = this._findRegion(cells.first);
|
||||
const [lastIntersect, lastIntersectIdx] = this._findRegion(cells.last);
|
||||
|
||||
// Fast-path if the cells to add are already all present in the mask. We
|
||||
// will otherwise need to do some mutation.
|
||||
if (firstIntersectIdx === lastIntersectIdx && !firstIntersect.isSpacer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We need to replace the existing covered regions with 1-3 new regions
|
||||
// depending whether we need to split spacers out of overlapping regions.
|
||||
const newLeadRegion: Array<CellRegion> = [];
|
||||
const newTailRegion: Array<CellRegion> = [];
|
||||
const newMainRegion: CellRegion = {
|
||||
...cells,
|
||||
isSpacer: false,
|
||||
};
|
||||
|
||||
if (firstIntersect.first < newMainRegion.first) {
|
||||
if (firstIntersect.isSpacer) {
|
||||
newLeadRegion.push({
|
||||
first: firstIntersect.first,
|
||||
last: newMainRegion.first - 1,
|
||||
isSpacer: true,
|
||||
});
|
||||
} else {
|
||||
newMainRegion.first = firstIntersect.first;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastIntersect.last > newMainRegion.last) {
|
||||
if (lastIntersect.isSpacer) {
|
||||
newTailRegion.push({
|
||||
first: newMainRegion.last + 1,
|
||||
last: lastIntersect.last,
|
||||
isSpacer: true,
|
||||
});
|
||||
} else {
|
||||
newMainRegion.last = lastIntersect.last;
|
||||
}
|
||||
}
|
||||
|
||||
const replacementRegions: Array<CellRegion> = [
|
||||
...newLeadRegion,
|
||||
newMainRegion,
|
||||
...newTailRegion,
|
||||
];
|
||||
const numRegionsToDelete = lastIntersectIdx - firstIntersectIdx + 1;
|
||||
this._regions.splice(
|
||||
firstIntersectIdx,
|
||||
numRegionsToDelete,
|
||||
...replacementRegions,
|
||||
);
|
||||
}
|
||||
|
||||
numCells(): number {
|
||||
return this._numCells;
|
||||
}
|
||||
|
||||
equals(other: CellRenderMask): boolean {
|
||||
return (
|
||||
this._numCells === other._numCells &&
|
||||
this._regions.length === other._regions.length &&
|
||||
this._regions.every(
|
||||
(region, i) =>
|
||||
region.first === other._regions[i].first &&
|
||||
region.last === other._regions[i].last &&
|
||||
region.isSpacer === other._regions[i].isSpacer,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
_findRegion(cellIdx: number): [CellRegion, number] {
|
||||
let firstIdx = 0;
|
||||
let lastIdx = this._regions.length - 1;
|
||||
|
||||
while (firstIdx <= lastIdx) {
|
||||
const middleIdx = Math.floor((firstIdx + lastIdx) / 2);
|
||||
const middleRegion = this._regions[middleIdx];
|
||||
|
||||
if (cellIdx >= middleRegion.first && cellIdx <= middleRegion.last) {
|
||||
return [middleRegion, middleIdx];
|
||||
} else if (cellIdx < middleRegion.first) {
|
||||
lastIdx = middleIdx - 1;
|
||||
} else if (cellIdx > middleRegion.last) {
|
||||
firstIdx = middleIdx + 1;
|
||||
}
|
||||
}
|
||||
|
||||
invariant(false, `A region was not found containing cellIdx ${cellIdx}`);
|
||||
}
|
||||
}
|
||||
72
packages/react-native-web/src/vendor/react-native/VirtualizedList/ChildListCollection.js
vendored
Normal file
72
packages/react-native-web/src/vendor/react-native/VirtualizedList/ChildListCollection.js
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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
|
||||
*/
|
||||
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
export default class ChildListCollection<TList> {
|
||||
_cellKeyToChildren: Map<string, Set<TList>> = new Map();
|
||||
_childrenToCellKey: Map<TList, string> = new Map();
|
||||
|
||||
add(list: TList, cellKey: string): void {
|
||||
invariant(
|
||||
!this._childrenToCellKey.has(list),
|
||||
'Trying to add already present child list',
|
||||
);
|
||||
|
||||
const cellLists = this._cellKeyToChildren.get(cellKey) ?? new Set();
|
||||
cellLists.add(list);
|
||||
this._cellKeyToChildren.set(cellKey, cellLists);
|
||||
|
||||
this._childrenToCellKey.set(list, cellKey);
|
||||
}
|
||||
|
||||
remove(list: TList): void {
|
||||
const cellKey = this._childrenToCellKey.get(list);
|
||||
invariant(cellKey != null, 'Trying to remove non-present child list');
|
||||
this._childrenToCellKey.delete(list);
|
||||
|
||||
const cellLists = this._cellKeyToChildren.get(cellKey);
|
||||
invariant(cellLists, '_cellKeyToChildren should contain cellKey');
|
||||
cellLists.delete(list);
|
||||
|
||||
if (cellLists.size === 0) {
|
||||
this._cellKeyToChildren.delete(cellKey);
|
||||
}
|
||||
}
|
||||
|
||||
forEach(fn: TList => void): void {
|
||||
for (const listSet of this._cellKeyToChildren.values()) {
|
||||
for (const list of listSet) {
|
||||
fn(list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
forEachInCell(cellKey: string, fn: TList => void): void {
|
||||
const listSet = this._cellKeyToChildren.get(cellKey) ?? [];
|
||||
for (const list of listSet) {
|
||||
fn(list);
|
||||
}
|
||||
}
|
||||
|
||||
anyInCell(cellKey: string, fn: TList => boolean): boolean {
|
||||
const listSet = this._cellKeyToChildren.get(cellKey) ?? [];
|
||||
for (const list of listSet) {
|
||||
if (fn(list)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
size(): number {
|
||||
return this._childrenToCellKey.size;
|
||||
}
|
||||
}
|
||||
85
packages/react-native-web/src/vendor/react-native/VirtualizedList/StateSafePureComponent.js
vendored
Normal file
85
packages/react-native-web/src/vendor/react-native/VirtualizedList/StateSafePureComponent.js
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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
|
||||
*/
|
||||
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import * as React from 'react';
|
||||
|
||||
/**
|
||||
* `setState` is called asynchronously, and should not rely on the value of
|
||||
* `this.props` or `this.state`:
|
||||
* https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
|
||||
*
|
||||
* SafePureComponent adds runtime enforcement, to catch cases where these
|
||||
* variables are read in a state updater function, instead of the ones passed
|
||||
* in.
|
||||
*/
|
||||
export default class StateSafePureComponent<
|
||||
Props,
|
||||
State: interface {},
|
||||
> extends React.PureComponent<Props, State> {
|
||||
_inAsyncStateUpdate = false;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this._installSetStateHooks();
|
||||
}
|
||||
|
||||
setState(
|
||||
partialState: ?($Shape<State> | ((State, Props) => ?$Shape<State>)),
|
||||
callback?: () => mixed,
|
||||
): void {
|
||||
if (typeof partialState === 'function') {
|
||||
super.setState((state, props) => {
|
||||
this._inAsyncStateUpdate = true;
|
||||
let ret;
|
||||
try {
|
||||
ret = partialState(state, props);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
} finally {
|
||||
this._inAsyncStateUpdate = false;
|
||||
}
|
||||
return ret;
|
||||
}, callback);
|
||||
} else {
|
||||
super.setState(partialState, callback);
|
||||
}
|
||||
}
|
||||
|
||||
_installSetStateHooks() {
|
||||
const that = this;
|
||||
let {props, state} = this;
|
||||
|
||||
Object.defineProperty(this, 'props', {
|
||||
get() {
|
||||
invariant(
|
||||
!that._inAsyncStateUpdate,
|
||||
'"this.props" should not be accessed during state updates',
|
||||
);
|
||||
return props;
|
||||
},
|
||||
set(newProps: Props) {
|
||||
props = newProps;
|
||||
},
|
||||
});
|
||||
Object.defineProperty(this, 'state', {
|
||||
get() {
|
||||
invariant(
|
||||
!that._inAsyncStateUpdate,
|
||||
'"this.state" should not be acceessed during state updates',
|
||||
);
|
||||
return state;
|
||||
},
|
||||
set(newState: State) {
|
||||
state = newState;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
247
packages/react-native-web/src/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.js
vendored
Normal file
247
packages/react-native-web/src/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.js
vendored
Normal file
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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
|
||||
*/
|
||||
|
||||
import type { LayoutEvent } from '../../../types';
|
||||
import type {
|
||||
FocusEvent,
|
||||
} from '../Types/CoreEventTypes';
|
||||
import type {CellRendererProps, RenderItemType} from './VirtualizedListProps';
|
||||
|
||||
import View, { type ViewProps } from '../../../exports/View';
|
||||
import StyleSheet from '../../../exports/StyleSheet';
|
||||
import {VirtualizedListCellContextProvider} from './VirtualizedListContext.js';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import * as React from 'react';
|
||||
|
||||
type ViewStyleProp = $PropertyType<ViewProps, 'style'>;
|
||||
|
||||
export type Props<ItemT> = {
|
||||
CellRendererComponent?: ?React.ComponentType<CellRendererProps<ItemT>>,
|
||||
ItemSeparatorComponent: ?React.ComponentType<
|
||||
any | {highlighted: boolean, leadingItem: ?ItemT},
|
||||
>,
|
||||
ListItemComponent?: ?(React.ComponentType<any> | React.Element<any>),
|
||||
cellKey: string,
|
||||
horizontal: ?boolean,
|
||||
index: number,
|
||||
inversionStyle: ViewStyleProp,
|
||||
item: ItemT,
|
||||
onCellLayout?: (event: LayoutEvent, cellKey: string, index: number) => void,
|
||||
onCellFocusCapture?: (event: FocusEvent) => void,
|
||||
onUnmount: (cellKey: string) => void,
|
||||
onUpdateSeparators: (
|
||||
cellKeys: Array<?string>,
|
||||
props: $Shape<SeparatorProps<ItemT>>,
|
||||
) => void,
|
||||
prevCellKey: ?string,
|
||||
renderItem?: ?RenderItemType<ItemT>,
|
||||
...
|
||||
};
|
||||
|
||||
type SeparatorProps<ItemT> = $ReadOnly<{|
|
||||
highlighted: boolean,
|
||||
leadingItem: ?ItemT,
|
||||
|}>;
|
||||
|
||||
type State<ItemT> = {
|
||||
separatorProps: SeparatorProps<ItemT>,
|
||||
...
|
||||
};
|
||||
|
||||
export default class CellRenderer<ItemT> extends React.Component<
|
||||
Props<ItemT>,
|
||||
State<ItemT>,
|
||||
> {
|
||||
state: State<ItemT> = {
|
||||
separatorProps: {
|
||||
highlighted: false,
|
||||
leadingItem: this.props.item,
|
||||
},
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(
|
||||
props: Props<ItemT>,
|
||||
prevState: State<ItemT>,
|
||||
): ?State<ItemT> {
|
||||
return {
|
||||
separatorProps: {
|
||||
...prevState.separatorProps,
|
||||
leadingItem: props.item,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: consider factoring separator stuff out of VirtualizedList into FlatList since it's not
|
||||
// reused by SectionList and we can keep VirtualizedList simpler.
|
||||
// $FlowFixMe[missing-local-annot]
|
||||
_separators = {
|
||||
highlight: () => {
|
||||
const {cellKey, prevCellKey} = this.props;
|
||||
this.props.onUpdateSeparators([cellKey, prevCellKey], {
|
||||
highlighted: true,
|
||||
});
|
||||
},
|
||||
unhighlight: () => {
|
||||
const {cellKey, prevCellKey} = this.props;
|
||||
this.props.onUpdateSeparators([cellKey, prevCellKey], {
|
||||
highlighted: false,
|
||||
});
|
||||
},
|
||||
updateProps: (
|
||||
select: 'leading' | 'trailing',
|
||||
newProps: SeparatorProps<ItemT>,
|
||||
) => {
|
||||
const {cellKey, prevCellKey} = this.props;
|
||||
this.props.onUpdateSeparators(
|
||||
[select === 'leading' ? prevCellKey : cellKey],
|
||||
newProps,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
updateSeparatorProps(newProps: SeparatorProps<ItemT>) {
|
||||
this.setState(state => ({
|
||||
separatorProps: {...state.separatorProps, ...newProps},
|
||||
}));
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.onUnmount(this.props.cellKey);
|
||||
}
|
||||
|
||||
_onLayout = (nativeEvent: LayoutEvent): void => {
|
||||
this.props.onCellLayout &&
|
||||
this.props.onCellLayout(
|
||||
nativeEvent,
|
||||
this.props.cellKey,
|
||||
this.props.index,
|
||||
);
|
||||
};
|
||||
|
||||
_renderElement(
|
||||
renderItem: ?RenderItemType<ItemT>,
|
||||
ListItemComponent: any,
|
||||
item: ItemT,
|
||||
index: number,
|
||||
): React.Node {
|
||||
if (renderItem && ListItemComponent) {
|
||||
console.warn(
|
||||
'VirtualizedList: Both ListItemComponent and renderItem props are present. ListItemComponent will take' +
|
||||
' precedence over renderItem.',
|
||||
);
|
||||
}
|
||||
|
||||
if (ListItemComponent) {
|
||||
/* $FlowFixMe[not-a-component] (>=0.108.0 site=react_native_fb) This
|
||||
* comment suppresses an error found when Flow v0.108 was deployed. To
|
||||
* see the error, delete this comment and run Flow. */
|
||||
/* $FlowFixMe[incompatible-type-arg] (>=0.108.0 site=react_native_fb)
|
||||
* This comment suppresses an error found when Flow v0.108 was deployed.
|
||||
* To see the error, delete this comment and run Flow. */
|
||||
return React.createElement(ListItemComponent, {
|
||||
item,
|
||||
index,
|
||||
separators: this._separators,
|
||||
});
|
||||
}
|
||||
|
||||
if (renderItem) {
|
||||
return renderItem({
|
||||
item,
|
||||
index,
|
||||
separators: this._separators,
|
||||
});
|
||||
}
|
||||
|
||||
invariant(
|
||||
false,
|
||||
'VirtualizedList: Either ListItemComponent or renderItem props are required but none were found.',
|
||||
);
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
const {
|
||||
CellRendererComponent,
|
||||
ItemSeparatorComponent,
|
||||
ListItemComponent,
|
||||
cellKey,
|
||||
horizontal,
|
||||
item,
|
||||
index,
|
||||
inversionStyle,
|
||||
onCellFocusCapture,
|
||||
onCellLayout,
|
||||
renderItem,
|
||||
} = this.props;
|
||||
const element = this._renderElement(
|
||||
renderItem,
|
||||
ListItemComponent,
|
||||
item,
|
||||
index,
|
||||
);
|
||||
|
||||
// NOTE: that when this is a sticky header, `onLayout` will get automatically extracted and
|
||||
// called explicitly by `ScrollViewStickyHeader`.
|
||||
const itemSeparator: React.Node = React.isValidElement(
|
||||
ItemSeparatorComponent,
|
||||
)
|
||||
? // $FlowFixMe[incompatible-type]
|
||||
ItemSeparatorComponent
|
||||
: // $FlowFixMe[incompatible-type]
|
||||
ItemSeparatorComponent && (
|
||||
<ItemSeparatorComponent {...this.state.separatorProps} />
|
||||
);
|
||||
const cellStyle = inversionStyle
|
||||
? horizontal
|
||||
? [styles.rowReverse, inversionStyle]
|
||||
: [styles.columnReverse, inversionStyle]
|
||||
: horizontal
|
||||
? [styles.row, inversionStyle]
|
||||
: inversionStyle;
|
||||
const result = !CellRendererComponent ? (
|
||||
<View
|
||||
style={cellStyle}
|
||||
onFocusCapture={onCellFocusCapture}
|
||||
{...(onCellLayout && {onLayout: this._onLayout})}>
|
||||
{element}
|
||||
{itemSeparator}
|
||||
</View>
|
||||
) : (
|
||||
<CellRendererComponent
|
||||
cellKey={cellKey}
|
||||
index={index}
|
||||
item={item}
|
||||
style={cellStyle}
|
||||
onFocusCapture={onCellFocusCapture}
|
||||
{...(onCellLayout && {onLayout: this._onLayout})}>
|
||||
{element}
|
||||
{itemSeparator}
|
||||
</CellRendererComponent>
|
||||
);
|
||||
|
||||
return (
|
||||
<VirtualizedListCellContextProvider cellKey={this.props.cellKey}>
|
||||
{result}
|
||||
</VirtualizedListCellContextProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
rowReverse: {
|
||||
flexDirection: 'row-reverse',
|
||||
},
|
||||
columnReverse: {
|
||||
flexDirection: 'column-reverse',
|
||||
},
|
||||
});
|
||||
@@ -8,37 +8,13 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type VirtualizedList from './';
|
||||
import typeof VirtualizedList from '../VirtualizedList';
|
||||
|
||||
import * as React from 'react';
|
||||
import {useMemo, useContext} from 'react';
|
||||
import {useContext, useMemo} from 'react';
|
||||
|
||||
const __DEV__ = process.env.NODE_ENV !== 'production';
|
||||
|
||||
type Frame = $ReadOnly<{
|
||||
offset: number,
|
||||
length: number,
|
||||
index: number,
|
||||
inLayout: boolean,
|
||||
}>;
|
||||
|
||||
export type ChildListState = $ReadOnly<{
|
||||
first: number,
|
||||
last: number,
|
||||
frames: {[key: number]: Frame},
|
||||
}>;
|
||||
|
||||
// Data propagated through nested lists (regardless of orientation) that is
|
||||
// useful for producing diagnostics for usage errors involving nesting (e.g
|
||||
// missing/duplicate keys).
|
||||
export type ListDebugInfo = $ReadOnly<{
|
||||
cellKey: string,
|
||||
listKey: string,
|
||||
parent: ?ListDebugInfo,
|
||||
// We include all ancestors regardless of orientation, so this is not always
|
||||
// identical to the child's orientation.
|
||||
horizontal: boolean,
|
||||
}>;
|
||||
|
||||
type Context = $ReadOnly<{
|
||||
cellKey: ?string,
|
||||
getScrollMetrics: () => {
|
||||
@@ -49,26 +25,21 @@ type Context = $ReadOnly<{
|
||||
timestamp: number,
|
||||
velocity: number,
|
||||
visibleLength: number,
|
||||
zoomScale: number,
|
||||
},
|
||||
horizontal: ?boolean,
|
||||
getOutermostParentListRef: () => VirtualizedList,
|
||||
getNestedChildState: string => ?ChildListState,
|
||||
getOutermostParentListRef: () => React.ElementRef<VirtualizedList>,
|
||||
registerAsNestedChild: ({
|
||||
cellKey: string,
|
||||
key: string,
|
||||
ref: VirtualizedList,
|
||||
parentDebugInfo: ListDebugInfo,
|
||||
}) => ?ChildListState,
|
||||
unregisterAsNestedChild: ({
|
||||
key: string,
|
||||
state: ChildListState,
|
||||
ref: React.ElementRef<VirtualizedList>,
|
||||
}) => void,
|
||||
unregisterAsNestedChild: ({
|
||||
ref: React.ElementRef<VirtualizedList>,
|
||||
}) => void,
|
||||
debugInfo: ListDebugInfo,
|
||||
}>;
|
||||
|
||||
export const VirtualizedListContext: React.Context<?Context> = React.createContext(
|
||||
null,
|
||||
);
|
||||
export const VirtualizedListContext: React.Context<?Context> =
|
||||
React.createContext(null);
|
||||
if (__DEV__) {
|
||||
VirtualizedListContext.displayName = 'VirtualizedListContext';
|
||||
}
|
||||
@@ -105,27 +76,15 @@ export function VirtualizedListContextProvider({
|
||||
getScrollMetrics: value.getScrollMetrics,
|
||||
horizontal: value.horizontal,
|
||||
getOutermostParentListRef: value.getOutermostParentListRef,
|
||||
getNestedChildState: value.getNestedChildState,
|
||||
registerAsNestedChild: value.registerAsNestedChild,
|
||||
unregisterAsNestedChild: value.unregisterAsNestedChild,
|
||||
debugInfo: {
|
||||
cellKey: value.debugInfo.cellKey,
|
||||
horizontal: value.debugInfo.horizontal,
|
||||
listKey: value.debugInfo.listKey,
|
||||
parent: value.debugInfo.parent,
|
||||
},
|
||||
}),
|
||||
[
|
||||
value.getScrollMetrics,
|
||||
value.horizontal,
|
||||
value.getOutermostParentListRef,
|
||||
value.getNestedChildState,
|
||||
value.registerAsNestedChild,
|
||||
value.unregisterAsNestedChild,
|
||||
value.debugInfo.cellKey,
|
||||
value.debugInfo.horizontal,
|
||||
value.debugInfo.listKey,
|
||||
value.debugInfo.parent,
|
||||
],
|
||||
);
|
||||
return (
|
||||
@@ -145,10 +104,14 @@ export function VirtualizedListCellContextProvider({
|
||||
cellKey: string,
|
||||
children: React.Node,
|
||||
}): React.Node {
|
||||
const context = useContext(VirtualizedListContext);
|
||||
// Avoid setting a newly created context object if the values are identical.
|
||||
const currContext = useContext(VirtualizedListContext);
|
||||
const context = useMemo(
|
||||
() => (currContext == null ? null : {...currContext, cellKey}),
|
||||
[currContext, cellKey],
|
||||
);
|
||||
return (
|
||||
<VirtualizedListContext.Provider
|
||||
value={context == null ? null : {...context, cellKey}}>
|
||||
<VirtualizedListContext.Provider value={context}>
|
||||
{children}
|
||||
</VirtualizedListContext.Provider>
|
||||
);
|
||||
|
||||
307
packages/react-native-web/src/vendor/react-native/VirtualizedList/VirtualizedListProps.js
vendored
Normal file
307
packages/react-native-web/src/vendor/react-native/VirtualizedList/VirtualizedListProps.js
vendored
Normal file
@@ -0,0 +1,307 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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
|
||||
*/
|
||||
|
||||
import ScrollView from '../../../exports/ScrollView';
|
||||
import type { LayoutEvent } from '../../../types';
|
||||
import type {
|
||||
FocusEvent,
|
||||
} from '../Types/CoreEventTypes';
|
||||
import { type ViewProps } from '../../../exports/View';
|
||||
type ViewStyleProp = $PropertyType<ViewProps, 'style'>;
|
||||
import type {
|
||||
ViewabilityConfig,
|
||||
ViewabilityConfigCallbackPair,
|
||||
ViewToken,
|
||||
} from '../ViewabilityHelper';
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
export type Item = any;
|
||||
|
||||
export type Separators = {
|
||||
highlight: () => void,
|
||||
unhighlight: () => void,
|
||||
updateProps: (select: 'leading' | 'trailing', newProps: Object) => void,
|
||||
...
|
||||
};
|
||||
|
||||
export type RenderItemProps<ItemT> = {
|
||||
item: ItemT,
|
||||
index: number,
|
||||
separators: Separators,
|
||||
...
|
||||
};
|
||||
|
||||
export type CellRendererProps<ItemT> = $ReadOnly<{
|
||||
cellKey: string,
|
||||
children: React.Node,
|
||||
index: number,
|
||||
item: ItemT,
|
||||
onFocusCapture?: (event: FocusEvent) => void,
|
||||
onLayout?: (event: LayoutEvent) => void,
|
||||
style: ViewStyleProp,
|
||||
}>;
|
||||
|
||||
export type RenderItemType<ItemT> = (
|
||||
info: RenderItemProps<ItemT>,
|
||||
) => React.Node;
|
||||
|
||||
type RequiredProps = {|
|
||||
/**
|
||||
* 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,
|
||||
/**
|
||||
* A generic accessor for extracting an item from any sort of data blob.
|
||||
*/
|
||||
getItem: (data: any, index: number) => ?Item,
|
||||
/**
|
||||
* Determines how many items are in the data blob.
|
||||
*/
|
||||
getItemCount: (data: any) => number,
|
||||
|};
|
||||
type OptionalProps = {|
|
||||
renderItem?: ?RenderItemType<Item>,
|
||||
/**
|
||||
* `debug` will turn on extra logging and visual overlays to aid with debugging both usage and
|
||||
* implementation, but with a significant perf hit.
|
||||
*/
|
||||
debug?: ?boolean,
|
||||
/**
|
||||
* 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. Defaults to false.
|
||||
*/
|
||||
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
|
||||
* `data` prop, stick it here and treat it immutably.
|
||||
*/
|
||||
extraData?: any,
|
||||
// e.g. height, y
|
||||
getItemLayout?: (
|
||||
data: any,
|
||||
index: number,
|
||||
) => {
|
||||
length: number,
|
||||
offset: number,
|
||||
index: number,
|
||||
...
|
||||
},
|
||||
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,
|
||||
keyExtractor?: ?(item: Item, index: number) => string,
|
||||
/**
|
||||
* CellRendererComponent allows customizing how cells rendered by
|
||||
* `renderItem`/`ListItemComponent` are wrapped when placed into the
|
||||
* underlying ScrollView. This component must accept event handlers which
|
||||
* notify VirtualizedList of changes within the cell.
|
||||
*/
|
||||
CellRendererComponent?: ?React.ComponentType<CellRendererProps<Item>>,
|
||||
/**
|
||||
* Rendered in between each item, but not at the top or bottom. By default, `highlighted` and
|
||||
* `leadingItem` props are provided. `renderItem` provides `separators.highlight`/`unhighlight`
|
||||
* which will update the `highlighted` prop, but you can also add custom props with
|
||||
* `separators.updateProps`.
|
||||
*/
|
||||
ItemSeparatorComponent?: ?React.ComponentType<any>,
|
||||
/**
|
||||
* 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'}]}
|
||||
* ListItemComponent={({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.
|
||||
*/
|
||||
ListItemComponent?: ?(React.ComponentType<any> | React.Element<any>),
|
||||
/**
|
||||
* Rendered when the list is empty. Can be a React Component Class, a render function, or
|
||||
* a rendered element.
|
||||
*/
|
||||
ListEmptyComponent?: ?(React.ComponentType<any> | React.Element<any>),
|
||||
/**
|
||||
* Rendered at the bottom of all the items. Can be a React Component Class, a render function, or
|
||||
* 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,
|
||||
/**
|
||||
* The maximum number of items to render in each incremental render batch. The more rendered at
|
||||
* once, the better the fill rate, but responsiveness may suffer because rendering content may
|
||||
* interfere with responding to button taps or other interactions.
|
||||
*/
|
||||
maxToRenderPerBatch?: ?number,
|
||||
/**
|
||||
* Called once when the scroll position gets within within `onEndReachedThreshold`
|
||||
* from the logical end of the list.
|
||||
*/
|
||||
onEndReached?: ?(info: {distanceFromEnd: number, ...}) => void,
|
||||
/**
|
||||
* How far from the end (in units of visible length of the list) the trailing edge of the
|
||||
* list must be from the end of the content to trigger the `onEndReached` callback.
|
||||
* Thus, a value of 0.5 will trigger `onEndReached` when the end of the content is
|
||||
* within half the visible length of the list.
|
||||
*/
|
||||
onEndReachedThreshold?: ?number,
|
||||
/**
|
||||
* If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make
|
||||
* sure to also set the `refreshing` prop correctly.
|
||||
*/
|
||||
onRefresh?: ?() => void,
|
||||
/**
|
||||
* Used to handle failures when scrolling to an index that has not been measured yet. Recommended
|
||||
* action is to either compute your own offset and `scrollTo` it, or scroll as far as possible and
|
||||
* then try again after more items have been rendered.
|
||||
*/
|
||||
onScrollToIndexFailed?: ?(info: {
|
||||
index: number,
|
||||
highestMeasuredFrameIndex: number,
|
||||
averageItemLength: number,
|
||||
...
|
||||
}) => void,
|
||||
/**
|
||||
* Called once when the scroll position gets within within `onStartReachedThreshold`
|
||||
* from the logical start of the list.
|
||||
*/
|
||||
onStartReached?: ?(info: {distanceFromStart: number, ...}) => void,
|
||||
/**
|
||||
* How far from the start (in units of visible length of the list) the leading edge of the
|
||||
* list must be from the start of the content to trigger the `onStartReached` callback.
|
||||
* Thus, a value of 0.5 will trigger `onStartReached` when the start of the content is
|
||||
* within half the visible length of the list.
|
||||
*/
|
||||
onStartReachedThreshold?: ?number,
|
||||
/**
|
||||
* Called when the viewability of rows changes, as defined by the
|
||||
* `viewabilityConfig` prop.
|
||||
*/
|
||||
onViewableItemsChanged?: ?(info: {
|
||||
viewableItems: Array<ViewToken>,
|
||||
changed: Array<ViewToken>,
|
||||
...
|
||||
}) => void,
|
||||
persistentScrollbar?: ?boolean,
|
||||
/**
|
||||
* Set this when offset is needed for the loading indicator to show correctly.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
refreshing?: ?boolean,
|
||||
/**
|
||||
* Note: may have bugs (missing content) in some circumstances - use at your own risk.
|
||||
*
|
||||
* This may improve scroll performance for large lists.
|
||||
*/
|
||||
removeClippedSubviews?: boolean,
|
||||
/**
|
||||
* Render a custom scroll component, e.g. with a differently styled `RefreshControl`.
|
||||
*/
|
||||
renderScrollComponent?: (props: Object) => React.Element<any>,
|
||||
/**
|
||||
* Amount of time between low-pri item render batches, e.g. for rendering items quite a ways off
|
||||
* screen. Similar fill rate/responsiveness tradeoff as `maxToRenderPerBatch`.
|
||||
*/
|
||||
updateCellsBatchingPeriod?: ?number,
|
||||
/**
|
||||
* See `ViewabilityHelper` for flow type and further documentation.
|
||||
*/
|
||||
viewabilityConfig?: ViewabilityConfig,
|
||||
/**
|
||||
* List of ViewabilityConfig/onViewableItemsChanged pairs. A specific onViewableItemsChanged
|
||||
* will be called when its corresponding ViewabilityConfig's conditions are met.
|
||||
*/
|
||||
viewabilityConfigCallbackPairs?: Array<ViewabilityConfigCallbackPair>,
|
||||
/**
|
||||
* Determines the maximum number of items rendered outside of the visible area, in units of
|
||||
* visible lengths. So if your list fills the screen, then `windowSize={21}` (the default) will
|
||||
* render the visible screen area plus up to 10 screens above and 10 below the viewport. Reducing
|
||||
* this number will reduce memory consumption and may improve performance, but will increase the
|
||||
* chance that fast scrolling may reveal momentary blank areas of unrendered content.
|
||||
*/
|
||||
windowSize?: ?number,
|
||||
/**
|
||||
* The legacy implementation is no longer supported.
|
||||
*/
|
||||
legacyImplementation?: empty,
|
||||
|};
|
||||
|
||||
export type Props = {|
|
||||
...React.ElementConfig<typeof ScrollView>,
|
||||
...RequiredProps,
|
||||
...OptionalProps,
|
||||
|};
|
||||
|
||||
/**
|
||||
* Subset of properties needed to calculate frame metrics
|
||||
*/
|
||||
export type FrameMetricProps = {
|
||||
data: RequiredProps['data'],
|
||||
getItemCount: RequiredProps['getItemCount'],
|
||||
getItem: RequiredProps['getItem'],
|
||||
getItemLayout?: OptionalProps['getItemLayout'],
|
||||
keyExtractor?: OptionalProps['keyExtractor'],
|
||||
...
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,15 +8,12 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ViewToken} from '../ViewabilityHelper';
|
||||
import {keyExtractor as defaultKeyExtractor} from '../VirtualizeUtils';
|
||||
import View from '../../../exports/View';
|
||||
import VirtualizedList from '../VirtualizedList';
|
||||
import * as React from 'react';
|
||||
|
||||
import {keyExtractor as defaultKeyExtractor} from '../VirtualizeUtils';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import * as React from 'react';
|
||||
|
||||
type Item = any;
|
||||
|
||||
@@ -141,9 +138,9 @@ class VirtualizedSectionList<
|
||||
return;
|
||||
}
|
||||
if (params.itemIndex > 0 && this.props.stickySectionHeadersEnabled) {
|
||||
// $FlowFixMe[prop-missing] Cannot access private property
|
||||
const frame = this._listRef._getFrameMetricsApprox(
|
||||
const frame = this._listRef.__getFrameMetricsApprox(
|
||||
index - params.itemIndex,
|
||||
this._listRef.props,
|
||||
);
|
||||
viewOffset += frame.length;
|
||||
}
|
||||
@@ -152,6 +149,7 @@ class VirtualizedSectionList<
|
||||
viewOffset,
|
||||
index,
|
||||
};
|
||||
// $FlowFixMe[incompatible-use]
|
||||
this._listRef.scrollToIndex(toIndexParams);
|
||||
}
|
||||
|
||||
@@ -174,7 +172,7 @@ class VirtualizedSectionList<
|
||||
const listHeaderOffset = this.props.ListHeaderComponent ? 1 : 0;
|
||||
|
||||
const stickyHeaderIndices = this.props.stickySectionHeadersEnabled
|
||||
? []
|
||||
? ([]: Array<number>)
|
||||
: undefined;
|
||||
|
||||
let itemCount = 0;
|
||||
@@ -239,6 +237,7 @@ class VirtualizedSectionList<
|
||||
return null;
|
||||
}
|
||||
|
||||
// $FlowFixMe[missing-local-annot]
|
||||
_keyExtractor = (item: Item, index: number) => {
|
||||
const info = this._subExtractor(index);
|
||||
return (info && info.key) || String(index);
|
||||
@@ -342,7 +341,8 @@ class VirtualizedSectionList<
|
||||
};
|
||||
|
||||
_renderItem =
|
||||
(listItemCount: number) =>
|
||||
(listItemCount: number): $FlowFixMe =>
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
({item, index}: {item: Item, index: number, ...}) => {
|
||||
const info = this._subExtractor(index);
|
||||
if (!info) {
|
||||
@@ -394,29 +394,33 @@ class VirtualizedSectionList<
|
||||
}
|
||||
};
|
||||
|
||||
_updatePropsFor = (cellKey, value) => {
|
||||
_updatePropsFor = (cellKey: string, value: any) => {
|
||||
const updateProps = this._updatePropsMap[cellKey];
|
||||
if (updateProps != null) {
|
||||
updateProps(value);
|
||||
}
|
||||
};
|
||||
|
||||
_updateHighlightFor = (cellKey, value) => {
|
||||
_updateHighlightFor = (cellKey: string, value: boolean) => {
|
||||
const updateHighlight = this._updateHighlightMap[cellKey];
|
||||
if (updateHighlight != null) {
|
||||
updateHighlight(value);
|
||||
}
|
||||
};
|
||||
|
||||
_setUpdateHighlightFor = (cellKey, updateHighlightFn) => {
|
||||
_setUpdateHighlightFor = (
|
||||
cellKey: string,
|
||||
updateHighlightFn: ?(boolean) => void,
|
||||
) => {
|
||||
if (updateHighlightFn != null) {
|
||||
this._updateHighlightMap[cellKey] = updateHighlightFn;
|
||||
} else {
|
||||
// $FlowFixMe[prop-missing]
|
||||
delete this._updateHighlightFor[cellKey];
|
||||
}
|
||||
};
|
||||
|
||||
_setUpdatePropsFor = (cellKey, updatePropsFn) => {
|
||||
_setUpdatePropsFor = (cellKey: string, updatePropsFn: ?(boolean) => void) => {
|
||||
if (updatePropsFn != null) {
|
||||
this._updatePropsMap[cellKey] = updatePropsFn;
|
||||
} else {
|
||||
@@ -448,10 +452,10 @@ class VirtualizedSectionList<
|
||||
return null;
|
||||
}
|
||||
|
||||
_updateHighlightMap = {};
|
||||
_updatePropsMap = {};
|
||||
_updateHighlightMap: {[string]: (boolean) => void} = {};
|
||||
_updatePropsMap: {[string]: void | (boolean => void)} = {};
|
||||
_listRef: ?React.ElementRef<typeof VirtualizedList>;
|
||||
_captureRef = ref => {
|
||||
_captureRef = (ref: null | React$ElementRef<Class<VirtualizedList>>) => {
|
||||
this._listRef = ref;
|
||||
};
|
||||
}
|
||||
@@ -525,6 +529,7 @@ function ItemWithSeparator(props: ItemWithSeparatorProps): React.Node {
|
||||
|
||||
React.useEffect(() => {
|
||||
setSelfHighlightCallback(cellKey, setSeparatorHighlighted);
|
||||
// $FlowFixMe[incompatible-call]
|
||||
setSelfUpdatePropsCallback(cellKey, setSeparatorProps);
|
||||
|
||||
return () => {
|
||||
@@ -598,4 +603,14 @@ function ItemWithSeparator(props: ItemWithSeparatorProps): React.Node {
|
||||
);
|
||||
}
|
||||
|
||||
export default VirtualizedSectionList;
|
||||
/* $FlowFixMe[class-object-subtyping] added when improving typing for this
|
||||
* parameters */
|
||||
// $FlowFixMe[method-unbinding]
|
||||
export default (VirtualizedSectionList: React.AbstractComponent<
|
||||
React.ElementConfig<typeof VirtualizedSectionList>,
|
||||
$ReadOnly<{
|
||||
getListRef: () => ?React.ElementRef<typeof VirtualizedList>,
|
||||
scrollToLocation: (params: ScrollToLocationParamsType) => void,
|
||||
...
|
||||
}>,
|
||||
>);
|
||||
|
||||
@@ -3,13 +3,17 @@
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
* @flow strict
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Intentional info-level logging for clear separation from ad-hoc console debug logging.
|
||||
*/
|
||||
function infoLog(...args) {
|
||||
function infoLog(...args: Array<mixed>): void {
|
||||
return console.log(...args);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user