diff --git a/packages/react-native-web/src/vendor/react-native/FlatList/index.js b/packages/react-native-web/src/vendor/react-native/FlatList/index.js index c9a4fa58..6e8f94ae 100644 --- a/packages/react-native-web/src/vendor/react-native/FlatList/index.js +++ b/packages/react-native-web/src/vendor/react-native/FlatList/index.js @@ -1,5 +1,5 @@ /** - * Copyright (c) Facebook, Inc. and its affiliates. + * 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. @@ -8,23 +8,26 @@ * @format */ -import type {ViewProps} from '../../../exports/View'; +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 invariant from 'fbjs/lib/invariant'; + +type ScrollViewNativeComponent = any; +type ScrollResponderType = any; +type ViewStyleProp = $PropertyType; import type { - ViewabilityConfig, ViewToken, ViewabilityConfigCallbackPair, } from '../ViewabilityHelper'; +import type {RenderItemType, RenderItemProps} from '../VirtualizedList'; +import {keyExtractor as defaultKeyExtractor} from '../VirtualizeUtils'; -import deepDiffer from '../deepDiffer'; -import * as React from 'react'; -import StyleSheet from '../../../exports/StyleSheet'; -import View from '../../../exports/View'; -import ScrollView from '../../../exports/ScrollView'; -import VirtualizedList, { type RenderItemType } from '../VirtualizedList'; - -type ViewStyleProp = $PropertyType; -import invariant from 'fbjs/lib/invariant'; +type $FlowFixMe = any; type RequiredProps = {| /** @@ -103,7 +106,7 @@ type OptionalProps = {| * 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, + 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 @@ -120,18 +123,43 @@ type OptionalProps = {| * 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, + 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. + * + * The default value is 1. */ - numColumns: number, + numColumns?: number, + /** + * Note: may have bugs (missing content) in some circumstances - use at your own risk. + * + * This may improve scroll performance for large lists. + * + * The default value is true for Android. + */ + removeClippedSubviews?: boolean, /** * See `ScrollView` for flow type and further documentation. */ fadingEdgeLength?: ?number, |}; +/** + * Default Props Helper Functions + * Use the following helper functions for default values + */ + +// removeClippedSubviewsOrDefault(this.props.removeClippedSubviews) +function removeClippedSubviewsOrDefault(removeClippedSubviews: ?boolean) { + return removeClippedSubviews ?? Platform.OS === 'android'; +} + +// numColumnsOrDefault(this.props.numColumns) +function numColumnsOrDefault(numColumns: ?number) { + return numColumns ?? 1; +} + type FlatListProps = {| ...RequiredProps, ...OptionalProps, @@ -155,12 +183,6 @@ export type Props = { ... }; -const defaultProps = { - ...VirtualizedList.defaultProps, - numColumns: 1, -}; -export type DefaultProps = typeof defaultProps; - /** * A performant interface for rendering simple, flat lists, supporting the most handy features: * @@ -270,7 +292,6 @@ export type DefaultProps = typeof defaultProps; * Also inherits [ScrollView Props](docs/scrollview.html#props), unless it is nested in another FlatList of same orientation. */ class FlatList extends React.PureComponent, void> { - static defaultProps: DefaultProps = defaultProps; props: Props; /** * Scrolls to the end of the content. May be janky without `getItemLayout` prop. @@ -354,7 +375,7 @@ class FlatList extends React.PureComponent, void> { /** * Provides a handle to the underlying scroll responder. */ - getScrollResponder(): ?typeof ScrollView { + getScrollResponder(): ?ScrollResponderType { if (this._listRef) { return this._listRef.getScrollResponder(); } @@ -365,7 +386,7 @@ class FlatList extends React.PureComponent, void> { */ getNativeScrollRef(): | ?React.ElementRef - | ?React.ElementRef { + | ?React.ElementRef { if (this._listRef) { /* $FlowFixMe[incompatible-return] Suppresses errors found when fixing * TextInput typing */ @@ -389,19 +410,18 @@ class FlatList extends React.PureComponent, void> { super(props); this._checkProps(this.props); if (this.props.viewabilityConfigCallbackPairs) { - this._virtualizedListPairs = this.props.viewabilityConfigCallbackPairs.map( - pair => ({ + 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. */ + /* $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. */ viewabilityConfig: this.props.viewabilityConfig, onViewableItemsChanged: this._createOnViewableItemsChanged( this.props.onViewableItemsChanged, @@ -442,16 +462,16 @@ class FlatList extends React.PureComponent, void> { _checkProps(props: Props) { const { - // $FlowFixMe this prop doesn't exist, is only used for an invariant + // $FlowFixMe[prop-missing] this prop doesn't exist, is only used for an invariant getItem, - // $FlowFixMe this prop doesn't exist, is only used for an invariant + // $FlowFixMe[prop-missing] this prop doesn't exist, is only used for an invariant getItemCount, horizontal, - numColumns, columnWrapperStyle, onViewableItemsChanged, viewabilityConfigCallbackPairs, } = props; + const numColumns = numColumnsOrDefault(this.props.numColumns); invariant( !getItem && !getItemCount, 'FlatList does not support custom data formats.', @@ -472,7 +492,7 @@ class FlatList extends React.PureComponent, void> { } _getItem = (data: Array, index: number) => { - const {numColumns} = this.props; + const numColumns = numColumnsOrDefault(this.props.numColumns); if (numColumns > 1) { const ret = []; for (let kk = 0; kk < numColumns; kk++) { @@ -489,7 +509,7 @@ class FlatList extends React.PureComponent, void> { _getItemCount = (data: ?Array): number => { if (data) { - const {numColumns} = this.props; + const numColumns = numColumnsOrDefault(this.props.numColumns); return numColumns > 1 ? Math.ceil(data.length / numColumns) : data.length; } else { return 0; @@ -497,28 +517,33 @@ class FlatList extends React.PureComponent, void> { }; _keyExtractor = (items: ItemT | Array, index: number) => { - const {keyExtractor, numColumns} = this.props; + const numColumns = numColumnsOrDefault(this.props.numColumns); + const keyExtractor = this.props.keyExtractor ?? defaultKeyExtractor; + 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 - // $FlowFixMe[incompatible-call] - .map((it, kk) => keyExtractor(it, index * numColumns + kk)) - .join(':') - ); + 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 Can't call keyExtractor with an array + // $FlowFixMe[incompatible-call] Can't call keyExtractor with an array return keyExtractor(items, index); } }; _pushMultiColumnViewable(arr: Array, v: ViewToken): void { - const {numColumns, keyExtractor} = this.props; + const numColumns = numColumnsOrDefault(this.props.numColumns); + const keyExtractor = this.props.keyExtractor ?? defaultKeyExtractor; v.item.forEach((item, ii) => { invariant(v.index != null, 'Missing index!'); const index = v.index * numColumns + ii; @@ -538,7 +563,7 @@ class FlatList extends React.PureComponent, void> { changed: Array, ... }) => { - const {numColumns} = this.props; + const numColumns = numColumnsOrDefault(this.props.numColumns); if (onViewableItemsChanged) { if (numColumns > 1) { const changed = []; @@ -556,12 +581,8 @@ class FlatList extends React.PureComponent, void> { } _renderer = () => { - const { - ListItemComponent, - renderItem, - numColumns, - columnWrapperStyle, - } = this.props; + const {ListItemComponent, renderItem, columnWrapperStyle} = this.props; + const numColumns = numColumnsOrDefault(this.props.numColumns); let virtualizedListRenderKey = ListItemComponent ? 'ListItemComponent' @@ -569,7 +590,9 @@ class FlatList extends React.PureComponent, void> { const renderer = (props): React.Node => { if (ListItemComponent) { - // $FlowFixMe Component isn't valid + // $FlowFixMe[not-a-component] Component isn't valid + // $FlowFixMe[incompatible-type-arg] Component isn't valid + // $FlowFixMe[incompatible-return] Component isn't valid return ; } else if (renderItem) { // $FlowFixMe[incompatible-call] @@ -580,9 +603,9 @@ class FlatList extends React.PureComponent, void> { }; 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. */ + /* $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) => { if (numColumns > 1) { const {item, index} = info; @@ -612,7 +635,12 @@ class FlatList extends React.PureComponent, void> { }; render(): React.Node { - const {numColumns, columnWrapperStyle, ...restProps} = this.props; + const { + numColumns, + columnWrapperStyle, + removeClippedSubviews: _removeClippedSubviews, + ...restProps + } = this.props; return ( extends React.PureComponent, void> { keyExtractor={this._keyExtractor} ref={this._captureRef} viewabilityConfigCallbackPairs={this._virtualizedListPairs} + removeClippedSubviews={removeClippedSubviewsOrDefault( + _removeClippedSubviews, + )} {...this._renderer()} /> ); diff --git a/packages/react-native-web/src/vendor/react-native/SectionList/index.js b/packages/react-native-web/src/vendor/react-native/SectionList/index.js index 3f33756e..d6957eb6 100644 --- a/packages/react-native-web/src/vendor/react-native/SectionList/index.js +++ b/packages/react-native-web/src/vendor/react-native/SectionList/index.js @@ -1,5 +1,5 @@ /** - * Copyright (c) Facebook, Inc. and its affiliates. + * 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. @@ -7,27 +7,27 @@ * @flow * @format */ + 'use strict'; import Platform from '../../../exports/Platform'; import * as React from 'react'; -import ScrollView from '../../../exports/ScrollView'; import VirtualizedSectionList from '../VirtualizedSectionList'; -import type { ScrollToLocationParamsType } from '../VirtualizedSectionList'; -import type {ViewToken} from '../ViewabilityHelper'; +type ScrollResponderType = any; import type { SectionBase as _SectionBase, Props as VirtualizedSectionListProps, + ScrollToLocationParamsType, } from '../VirtualizedSectionList'; type Item = any; export type SectionBase = _SectionBase; -type RequiredProps> = { +type RequiredProps> = {| /** - * The actual data to render, akin to the `data` prop in [``](/react-native/docs/flatlist.html). + * The actual data to render, akin to the `data` prop in [``](https://reactnative.dev/docs/flatlist). * * General shape: * @@ -38,9 +38,9 @@ type RequiredProps> = { * }> */ sections: $ReadOnlyArray, -}; +|}; -type OptionalProps> = { +type OptionalProps> = {| /** * Default renderer for every item in every section. Can be over-ridden on a per-section basis. */ @@ -52,38 +52,10 @@ type OptionalProps> = { highlight: () => void, unhighlight: () => void, updateProps: (select: 'leading' | 'trailing', newProps: Object) => void, + ... }, - }) => ?React.Element, - /** - * Rendered in between each item, but not at the top or bottom. By default, `highlighted`, - * `section`, and `[leading/trailing][Item/Separator]` 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, - /** - * Rendered at the very beginning of the list. Can be a React Component Class, a render function, or - * a rendered element. - */ - ListHeaderComponent?: ?(React.ComponentType | React.Element), - /** - * Rendered when the list is empty. Can be a React Component Class, a render function, or - * a rendered element. - */ - ListEmptyComponent?: ?(React.ComponentType | React.Element), - /** - * Rendered at the very end of the list. Can be a React Component Class, a render function, or - * a rendered element. - */ - ListFooterComponent?: ?(React.ComponentType | React.Element), - /** - * 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, + ... + }) => null | React.Element, /** * 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 @@ -95,7 +67,7 @@ type OptionalProps> = { * 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, + initialNumToRender?: ?number, /** * Reverses the direction of scroll. Uses scale transforms of -1. */ @@ -106,73 +78,43 @@ type OptionalProps> = { * 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, + keyExtractor?: ?(item: Item, index: number) => string, /** * Called once when the scroll position gets within `onEndReachedThreshold` of the rendered * content. */ - onEndReached?: ?(info: {distanceFromEnd: number}) => void, - /** - * How far from the end (in units of visible length of the list) the bottom 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, - /** - * Called when the viewability of rows changes, as defined by the - * `viewabilityConfig` prop. - */ - onViewableItemsChanged?: ?(info: { - viewableItems: Array, - changed: Array, - }) => void, - /** - * Set this true while waiting for new data from a refresh. - */ - refreshing?: ?boolean, + 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, - /** - * 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}) => ?React.Element, - /** - * Rendered at the bottom of each section. - */ - renderSectionFooter?: ?(info: {section: SectionT}) => ?React.Element, - /** - * 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, +|}; - /** - * The legacy implementation is no longer supported. - */ - legacyImplementation?: empty, -}; - -export type Props = RequiredProps & - OptionalProps & - VirtualizedSectionListProps; - -const defaultProps = { - ...VirtualizedSectionList.defaultProps, - stickySectionHeadersEnabled: Platform.OS === 'ios', -}; - -type DefaultProps = typeof defaultProps; +export type Props = {| + ...$Diff< + VirtualizedSectionListProps, + { + getItem: $PropertyType, 'getItem'>, + getItemCount: $PropertyType< + VirtualizedSectionListProps, + 'getItemCount', + >, + renderItem: $PropertyType< + VirtualizedSectionListProps, + 'renderItem', + >, + keyExtractor: $PropertyType< + VirtualizedSectionListProps, + 'keyExtractor', + >, + ... + }, + >, + ...RequiredProps, + ...OptionalProps, +|}; /** * A performant interface for rendering sectioned lists, supporting the most handy features: @@ -189,7 +131,7 @@ type DefaultProps = typeof defaultProps; * - Scroll loading. * * If you don't need section support and want a simpler interface, use - * [``](/react-native/docs/flatlist.html). + * [``](https://reactnative.dev/docs/flatlist). * * Simple Examples: * @@ -211,7 +153,7 @@ type DefaultProps = typeof defaultProps; * ]} * /> * - * This is a convenience wrapper around [``](docs/virtualizedlist.html), + * This is a convenience wrapper around [``](docs/virtualizedlist), * and thus inherits its props (as well as those of `ScrollView`) that aren't explicitly listed * here, along with the following caveats: * @@ -229,12 +171,10 @@ type DefaultProps = typeof defaultProps; * Alternatively, you can provide a custom `keyExtractor` prop. * */ -class SectionList> extends React.PureComponent< - Props, - void, -> { +export default class SectionList< + SectionT: SectionBase, +> extends React.PureComponent, void> { props: Props; - static defaultProps: DefaultProps = defaultProps; /** * Scrolls to the item at the specified `sectionIndex` and `itemIndex` (within the section) @@ -275,14 +215,14 @@ class SectionList> extends React.PureComponent< /** * Provides a handle to the underlying scroll responder. */ - getScrollResponder(): ?typeof ScrollView { + getScrollResponder(): ?ScrollResponderType { const listRef = this._wrapperListRef && this._wrapperListRef.getListRef(); if (listRef) { return listRef.getScrollResponder(); } } - getScrollableNode(): any | void { + getScrollableNode(): any { const listRef = this._wrapperListRef && this._wrapperListRef.getListRef(); if (listRef) { return listRef.getScrollableNode(); @@ -297,12 +237,16 @@ class SectionList> extends React.PureComponent< } render(): React.Node { + const { + stickySectionHeadersEnabled: _stickySectionHeadersEnabled, + ...restProps + } = this.props; + const stickySectionHeadersEnabled = + _stickySectionHeadersEnabled ?? Platform.OS === 'ios'; return ( - /* $FlowFixMe(>=0.66.0 site=react_native_fb) This comment suppresses an - * error found when Flow v0.66 was deployed. To see the error delete this - * comment and run Flow. */ items.length} getItem={(items, index) => items[index]} @@ -311,11 +255,7 @@ class SectionList> extends React.PureComponent< } _wrapperListRef: ?React.ElementRef; - // $FlowFixMe - _captureRef: ((ref: any) => void) = ref => { - // $FlowFixMe + _captureRef = ref => { this._wrapperListRef = ref; }; -} - -export default SectionList; +} \ No newline at end of file diff --git a/packages/react-native-web/src/vendor/react-native/VirtualizeUtils/index.js b/packages/react-native-web/src/vendor/react-native/VirtualizeUtils/index.js index 2c497746..f696472a 100644 --- a/packages/react-native-web/src/vendor/react-native/VirtualizeUtils/index.js +++ b/packages/react-native-web/src/vendor/react-native/VirtualizeUtils/index.js @@ -1,5 +1,5 @@ /** - * Copyright (c) Facebook, Inc. and its affiliates. + * 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. @@ -7,6 +7,7 @@ * @flow * @format */ + 'use strict'; import invariant from 'fbjs/lib/invariant'; @@ -16,10 +17,14 @@ import invariant from 'fbjs/lib/invariant'; * items that bound different windows of content, such as the visible area or the buffered overscan * area. */ -function elementsThatOverlapOffsets( +export function elementsThatOverlapOffsets( offsets: Array, itemCount: number, - getFrameMetrics: (index: number) => {length: number, offset: number}, + getFrameMetrics: (index: number) => { + length: number, + offset: number, + ... + }, ): Array { const out = []; let outLength = 0; @@ -50,9 +55,17 @@ function elementsThatOverlapOffsets( * 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}, +export function newRangeCount( + prev: { + first: number, + last: number, + ... + }, + next: { + first: number, + last: number, + ... + }, ): number { return ( next.last - @@ -71,23 +84,33 @@ function newRangeCount( * 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, +export function computeWindowedRenderLimits( + data: any, + getItemCount: (data: any) => number, + maxToRenderPerBatch: number, + windowSize: number, + prev: { + first: number, + last: number, + ... + }, + getFrameMetricsApprox: (index: number) => { + length: number, + offset: 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; +): { + first: number, + last: number, + ... +} { const itemCount = getItemCount(data); if (itemCount === 0) { return prev; @@ -125,7 +148,7 @@ 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], - props.getItemCount(props.data), + itemCount, getFrameMetricsApprox, ); overscanFirst = overscanFirst == null ? 0 : overscanFirst; @@ -207,16 +230,12 @@ function computeWindowedRenderLimits( return {first, last}; } -const VirtualizeUtils = { - computeWindowedRenderLimits, - elementsThatOverlapOffsets, - newRangeCount, -}; - -export { - computeWindowedRenderLimits, - elementsThatOverlapOffsets, - newRangeCount, -} - -export default VirtualizeUtils; +export function keyExtractor(item: any, index: number): string { + if (typeof item === 'object' && item?.key != null) { + return item.key; + } + if (typeof item === 'object' && item?.id != null) { + return item.id; + } + return String(index); +} \ No newline at end of file diff --git a/packages/react-native-web/src/vendor/react-native/VirtualizedList/index.js b/packages/react-native-web/src/vendor/react-native/VirtualizedList/index.js index 836f40c4..180ec22a 100644 --- a/packages/react-native-web/src/vendor/react-native/VirtualizedList/index.js +++ b/packages/react-native-web/src/vendor/react-native/VirtualizedList/index.js @@ -1,5 +1,5 @@ /** - * Copyright (c) Facebook, Inc. and its affiliates. + * 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. @@ -8,30 +8,33 @@ * @format */ -import type {ViewProps} from '../../../exports/View'; +import Batchinator from '../Batchinator'; +import FillRateHelper from '../FillRateHelper'; +import findNodeHandle from '../../../exports/findNodeHandle'; +import RefreshControl from '../../../exports/RefreshControl'; +import ScrollView from '../../../exports/ScrollView'; +import StyleSheet from '../../../exports/StyleSheet'; +import View from '../../../exports/View'; +import ViewabilityHelper from '../ViewabilityHelper'; + +const flattenStyle = StyleSheet.flatten; +import infoLog from '../infoLog'; +import invariant from 'fbjs/lib/invariant'; + +import { + keyExtractor as defaultKeyExtractor, + computeWindowedRenderLimits, +} from '../VirtualizeUtils'; + +import * as React from 'react'; +type ScrollResponderType = any; +import type { ViewProps } from '../../../exports/View'; +type ViewStyleProp = $PropertyType; import type { ViewabilityConfig, ViewToken, ViewabilityConfigCallbackPair, } from '../ViewabilityHelper'; - -import Batchinator from '../Batchinator'; -import FillRateHelper from '../FillRateHelper'; -import PropTypes from 'prop-types'; -import * as React from 'react'; -import RefreshControl from '../../../exports/RefreshControl'; -import ScrollView from '../../../exports/ScrollView'; -import StyleSheet from '../../../exports/StyleSheet'; -import UIManager from '../../../exports/UIManager'; -import View from '../../../exports/View'; -import ViewabilityHelper from '../ViewabilityHelper'; - -import findNodeHandle from '../../../exports/findNodeHandle'; -const flattenStyle = StyleSheet.flatten; -import infoLog from '../infoLog'; -import invariant from 'fbjs/lib/invariant'; -import warning from 'fbjs/lib/warning'; -import { computeWindowedRenderLimits } from '../VirtualizeUtils'; import { VirtualizedListCellContextProvider, VirtualizedListContext, @@ -41,9 +44,6 @@ import { } from './VirtualizedListContext.js'; type Item = any; -type ViewStyleProp = $PropertyType; - -export type renderItemType = (info: any) => ?React.Element; const __DEV__ = process.env.NODE_ENV !== 'production'; @@ -100,7 +100,7 @@ type OptionalProps = {| /** * 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. + * this for debugging purposes. Defaults to false. */ disableVirtualization?: ?boolean, /** @@ -125,7 +125,7 @@ type OptionalProps = {| * 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, + 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 @@ -137,7 +137,7 @@ type OptionalProps = {| * Reverses the direction of scroll. Uses scale transforms of -1. */ inverted?: ?boolean, - keyExtractor: (item: Item, index: number) => string, + keyExtractor?: ?(item: Item, index: number) => string, /** * Each cell is rendered using this element. Can be a React Component Class, * or a render function. Defaults to using View. @@ -211,7 +211,7 @@ type OptionalProps = {| * 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, + maxToRenderPerBatch?: ?number, /** * Called once when the scroll position gets within `onEndReachedThreshold` of the rendered * content. @@ -252,7 +252,6 @@ type OptionalProps = {| persistentScrollbar?: ?boolean, /** * Set this when offset is needed for the loading indicator to show correctly. - * @platform android */ progressViewOffset?: number, /** @@ -279,7 +278,7 @@ type OptionalProps = {| * 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, + updateCellsBatchingPeriod?: ?number, /** * See `ViewabilityHelper` for flow type and further documentation. */ @@ -296,7 +295,7 @@ type OptionalProps = {| * 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, + windowSize?: ?number, /** * The legacy implementation is no longer supported. */ @@ -304,24 +303,11 @@ type OptionalProps = {| |}; type Props = {| - // $FlowFixMe: View should be changed to an exact type in the future ...React.ElementConfig, ...RequiredProps, ...OptionalProps, |}; -type DefaultProps = {| - disableVirtualization: boolean, - horizontal: boolean, - initialNumToRender: number, - keyExtractor: (item: Item, index: number) => string, - maxToRenderPerBatch: number, - onEndReachedThreshold: number, - scrollEventThrottle: number, - updateCellsBatchingPeriod: number, - windowSize: number, -|}; - let _usedIndexForKey = false; let _keylessItemComponentName: string = ''; @@ -331,8 +317,43 @@ type State = { }; /** - * Base implementation for the more convenient [``](https://reactnative.dev/docs/flatlist.html) - * and [``](https://reactnative.dev/docs/sectionlist.html) components, which are also better + * Default Props Helper Functions + * Use the following helper functions for default values + */ + +// horizontalOrDefault(this.props.horizontal) +function horizontalOrDefault(horizontal: ?boolean) { + return horizontal ?? false; +} + +// initialNumToRenderOrDefault(this.props.initialNumToRenderOrDefault) +function initialNumToRenderOrDefault(initialNumToRender: ?number) { + return initialNumToRender ?? 10; +} + +// maxToRenderPerBatchOrDefault(this.props.maxToRenderPerBatch) +function maxToRenderPerBatchOrDefault(maxToRenderPerBatch: ?number) { + return maxToRenderPerBatch ?? 10; +} + +// onEndReachedThresholdOrDefault(this.props.onEndReachedThreshold) +function onEndReachedThresholdOrDefault(onEndReachedThreshold: ?number) { + return onEndReachedThreshold ?? 2; +} + +// scrollEventThrottleOrDefault(this.props.scrollEventThrottle) +function scrollEventThrottleOrDefault(scrollEventThrottle: ?number) { + return scrollEventThrottle ?? 50; +} + +// windowSizeOrDefault(this.props.windowSize) +function windowSizeOrDefault(windowSize: ?number) { + return windowSize ?? 21; +} + +/** + * Base implementation for the more convenient [``](https://reactnative.dev/docs/flatlist) + * and [``](https://reactnative.dev/docs/sectionlist) components, which are also better * documented. In general, this should only really be used if you need more flexibility than * `FlatList` provides, e.g. for use with immutable data instead of plain arrays. * @@ -356,6 +377,7 @@ type State = { * and we are working on improving it behind the scenes. * - By default, the list looks for a `key` or `id` prop on each item and uses that for the React key. * Alternatively, you can provide a custom `keyExtractor` prop. + * - As an effort to remove defaultProps, use helper functions when referencing certain props * */ class VirtualizedList extends React.PureComponent { @@ -388,7 +410,9 @@ class VirtualizedList extends React.PureComponent { } this._scrollRef.scrollTo( - this.props.horizontal ? {x: offset, animated} : {y: offset, animated}, + horizontalOrDefault(this.props.horizontal) + ? {x: offset, animated} + : {y: offset, animated}, ); } @@ -420,9 +444,9 @@ class VirtualizedList extends React.PureComponent { ); invariant( index < getItemCount(data), - `scrollToIndex out of range: requested index ${index} is out of 0 to ${getItemCount( - data, - ) - 1}`, + `scrollToIndex out of range: requested index ${index} is out of 0 to ${ + getItemCount(data) - 1 + }`, ); if (!getItemLayout && index > this._highestMeasuredFrameIndex) { invariant( @@ -510,7 +534,9 @@ class VirtualizedList extends React.PureComponent { } this._scrollRef.scrollTo( - this.props.horizontal ? {x: offset, animated} : {y: offset, animated}, + horizontalOrDefault(this.props.horizontal) + ? {x: offset, animated} + : {y: offset, animated}, ); } @@ -537,7 +563,7 @@ class VirtualizedList extends React.PureComponent { * Note that `this._scrollRef` might not be a `ScrollView`, so we * need to check that it responds to `getScrollResponder` before calling it. */ - getScrollResponder(): ?typeof ScrollView { + getScrollResponder(): ?ScrollResponderType { if (this._scrollRef && this._scrollRef.getScrollResponder) { return this._scrollRef.getScrollResponder(); } @@ -567,30 +593,6 @@ class VirtualizedList extends React.PureComponent { } } - static defaultProps: DefaultProps = { - disableVirtualization: false, - horizontal: false, - initialNumToRender: 10, - keyExtractor: (item: Item, index: number) => { - if (item.key != null) { - return item.key; - } - if (item.id != null) { - return item.id; - } - _usedIndexForKey = true; - if (item.type && item.type.displayName) { - _keylessItemComponentName = item.type.displayName; - } - return String(index); - }, - maxToRenderPerBatch: 10, - onEndReachedThreshold: 2, // multiples of length - scrollEventThrottle: 50, - updateCellsBatchingPeriod: 50, - windowSize: 21, // multiples of length - }; - _getCellKey(): string { return this.context?.cellKey || 'rootList'; } @@ -603,7 +605,7 @@ class VirtualizedList extends React.PureComponent { return { listKey: this._getListKey(), cellKey: this._getCellKey(), - horizontal: !!this.props.horizontal, + horizontal: horizontalOrDefault(this.props.horizontal), parent: this.context?.debugInfo, }; } @@ -678,26 +680,26 @@ class VirtualizedList extends React.PureComponent { state: State; + // REACT-NATIVE-WEB patch to preserve during future RN merges: Support inverted wheel scroller. invertedWheelEventHandler: ?(ev: any) => void; constructor(props: Props) { super(props); invariant( - // $FlowFixMe + // $FlowFixMe[prop-missing] !props.onScroll || !props.onScroll.__isNative, 'Components based on VirtualizedList must be wrapped with Animated.createAnimatedComponent ' + 'to support native onScroll events with useNativeDriver', ); - invariant( - props.windowSize > 0, + windowSizeOrDefault(props.windowSize) > 0, 'VirtualizedList: The windowSize prop must be present and set to a value greater than 0.', ); this._fillRateHelper = new FillRateHelper(this._getFrameMetrics); this._updateCellsToRenderBatcher = new Batchinator( this._updateCellsToRender, - this.props.updateCellsBatchingPeriod, + this.props.updateCellsBatchingPeriod ?? 50, ); if (this.props.viewabilityConfigCallbackPairs) { @@ -708,10 +710,10 @@ class VirtualizedList extends React.PureComponent { }), ); } else if (this.props.onViewableItemsChanged) { - const onViewableItemsChanged = this.props.onViewableItemsChanged this._viewabilityTuples.push({ viewabilityHelper: new ViewabilityHelper(this.props.viewabilityConfig), - onViewableItemsChanged, + // $FlowFixMe[incompatible-call] + onViewableItemsChanged: this.props.onViewableItemsChanged, }); } @@ -720,7 +722,8 @@ class VirtualizedList extends React.PureComponent { last: Math.min( this.props.getItemCount(this.props.data), - (this.props.initialScrollIndex || 0) + this.props.initialNumToRender, + (this.props.initialScrollIndex || 0) + + initialNumToRenderOrDefault(this.props.initialNumToRender), ) - 1, }; @@ -733,6 +736,7 @@ class VirtualizedList extends React.PureComponent { } } + // REACT-NATIVE-WEB patch to preserve during future RN merges: Support inverted wheel scroller. // For issue https://github.com/necolas/react-native-web/issues/995 this.invertedWheelEventHandler = (ev: any) => { if (this.props.inverted && this._scrollRef && this._scrollRef.getScrollableNode) { @@ -762,6 +766,8 @@ class VirtualizedList extends React.PureComponent { parentDebugInfo: this.context.debugInfo, }); } + + // REACT-NATIVE-WEB patch to preserve during future RN merges: Support inverted wheel scroller. this.setupWebWheelHandler(); } @@ -782,13 +788,16 @@ class VirtualizedList extends React.PureComponent { tuple.viewabilityHelper.dispose(); }); this._fillRateHelper.deactivateAndFlush(); + + // REACT-NATIVE-WEB patch to preserve during future RN merges: Support inverted wheel scroller. this.teardownWebWheelHandler(); } + // REACT-NATIVE-WEB patch to preserve during future RN merges: Support inverted wheel scroller. setupWebWheelHandler() { if (this._scrollRef && this._scrollRef.getScrollableNode) { this._scrollRef.getScrollableNode().addEventListener('wheel', - this.invertedWheelEventHandler + this.invertedWheelEventHandler ); } else { setTimeout(() => this.setupWebWheelHandler(), 50); @@ -796,16 +805,20 @@ class VirtualizedList extends React.PureComponent { } } + // REACT-NATIVE-WEB patch to preserve during future RN merges: Support inverted wheel scroller. teardownWebWheelHandler() { if (this._scrollRef && this._scrollRef.getScrollableNode) { this._scrollRef.getScrollableNode().removeEventListener('wheel', - this.invertedWheelEventHandler + this.invertedWheelEventHandler ); } } static getDerivedStateFromProps(newProps: Props, prevState: State): State { - const {data, getItemCount, maxToRenderPerBatch} = newProps; + const {data, getItemCount} = newProps; + const maxToRenderPerBatch = maxToRenderPerBatchOrDefault( + newProps.maxToRenderPerBatch, + ); // first and last could be stale (e.g. if a new, shorter items props is passed in), so we make // sure we're rendering a reasonable range here. return { @@ -832,7 +845,6 @@ class VirtualizedList extends React.PureComponent { getItem, getItemCount, horizontal, - keyExtractor, } = this.props; const stickyOffset = this.props.ListHeaderComponent ? 1 : 0; const end = getItemCount(data) - 1; @@ -840,7 +852,7 @@ class VirtualizedList extends React.PureComponent { last = Math.min(end, last); for (let ii = first; ii <= last; ii++) { const item = getItem(data, ii); - const key = keyExtractor(item, ii); + const key = this._keyExtractor(item, ii); this._indicesToKeys.set(ii, key); if (stickyIndicesFromProps.has(ii + stickyOffset)) { stickyHeaderIndices.push(cells.length); @@ -884,10 +896,29 @@ class VirtualizedList extends React.PureComponent { _isNestedWithSameOrientation(): boolean { const nestedContext = this.context; return !!( - nestedContext && !!nestedContext.horizontal === !!this.props.horizontal + nestedContext && + !!nestedContext.horizontal === horizontalOrDefault(this.props.horizontal) ); } + _getSpacerKey = (isVertical: boolean): string => + isVertical ? 'height' : 'width'; + + _keyExtractor(item: Item, index: number) { + if (this.props.keyExtractor != null) { + return this.props.keyExtractor(item, index); + } + + const key = defaultKeyExtractor(item, index); + if (key === String(index)) { + _usedIndexForKey = true; + if (item.type && item.type.displayName) { + _keylessItemComponentName = item.type.displayName; + } + } + return key; + } + render(): React.Node { if (__DEV__) { const flatStyles = flattenStyle(this.props.contentContainerStyle); @@ -898,15 +929,12 @@ class VirtualizedList extends React.PureComponent { ); } } - const { - ListEmptyComponent, - ListFooterComponent, - ListHeaderComponent, - } = this.props; + const {ListEmptyComponent, ListFooterComponent, ListHeaderComponent} = + this.props; const {data, horizontal} = this.props; const isVirtualizationDisabled = this._isVirtualizationDisabled(); const inversionStyle = this.props.inverted - ? this.props.horizontal + ? horizontalOrDefault(this.props.horizontal) ? styles.horizontallyInverted : styles.verticallyInverted : null; @@ -920,7 +948,8 @@ class VirtualizedList extends React.PureComponent { const element = React.isValidElement(ListHeaderComponent) ? ( ListHeaderComponent ) : ( - // $FlowFixMe + // $FlowFixMe[not-a-component] + // $FlowFixMe[incompatible-type-arg] ); cells.push( @@ -934,7 +963,7 @@ class VirtualizedList extends React.PureComponent { this.props.ListHeaderComponentStyle, )}> { - // $FlowFixMe - Typing ReactNativeComponent revealed errors + // $FlowFixMe[incompatible-type] - Typing ReactNativeComponent revealed errors element } @@ -945,10 +974,10 @@ class VirtualizedList extends React.PureComponent { if (itemCount > 0) { _usedIndexForKey = false; _keylessItemComponentName = ''; - const spacerKey = !horizontal ? 'height' : 'width'; + const spacerKey = this._getSpacerKey(!horizontal); const lastInitialIndex = this.props.initialScrollIndex ? -1 - : this.props.initialNumToRender - 1; + : initialNumToRenderOrDefault(this.props.initialNumToRender) - 1; const {first, last} = this.state; this._pushCells( cells, @@ -973,9 +1002,6 @@ class VirtualizedList extends React.PureComponent { initBlock.offset - (this.props.initialScrollIndex ? 0 : initBlock.length); cells.push( - /* $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. */ , ); this._pushCells( @@ -990,9 +1016,6 @@ class VirtualizedList extends React.PureComponent { this._getFrameMetricsApprox(first).offset - (stickyBlock.offset + stickyBlock.length); cells.push( - /* $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. */ , ); insertedStickySpacer = true; @@ -1006,9 +1029,6 @@ class VirtualizedList extends React.PureComponent { this._getFrameMetricsApprox(first).offset - (initBlock.offset + initBlock.length); cells.push( - /* $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. */ , ); } @@ -1043,9 +1063,6 @@ class VirtualizedList extends React.PureComponent { endFrame.length - (lastFrame.offset + lastFrame.length); cells.push( - /* $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. */ , ); } @@ -1055,7 +1072,8 @@ class VirtualizedList extends React.PureComponent { ) ? ( ListEmptyComponent ) : ( - // $FlowFixMe + // $FlowFixMe[not-a-component] + // $FlowFixMe[incompatible-type-arg] )): any); cells.push( @@ -1075,7 +1093,8 @@ class VirtualizedList extends React.PureComponent { const element = React.isValidElement(ListFooterComponent) ? ( ListFooterComponent ) : ( - // $FlowFixMe + // $FlowFixMe[not-a-component] + // $FlowFixMe[incompatible-type-arg] ); cells.push( @@ -1089,7 +1108,7 @@ class VirtualizedList extends React.PureComponent { this.props.ListFooterComponentStyle, )}> { - // $FlowFixMe - Typing ReactNativeComponent revealed errors + // $FlowFixMe[incompatible-type] - Typing ReactNativeComponent revealed errors element } @@ -1105,7 +1124,13 @@ class VirtualizedList extends React.PureComponent { onScrollEndDrag: this._onScrollEndDrag, onMomentumScrollBegin: this._onMomentumScrollBegin, onMomentumScrollEnd: this._onMomentumScrollEnd, - scrollEventThrottle: this.props.scrollEventThrottle, // TODO: Android support + scrollEventThrottle: scrollEventThrottleOrDefault( + this.props.scrollEventThrottle, + ), // TODO: Android support + invertStickyHeaders: + this.props.invertStickyHeaders !== undefined + ? this.props.invertStickyHeaders + : this.props.inverted, stickyHeaderIndices, style: inversionStyle ? [inversionStyle, this.props.style] @@ -1120,7 +1145,7 @@ class VirtualizedList extends React.PureComponent { value={{ cellKey: null, getScrollMetrics: this._getScrollMetrics, - horizontal: this.props.horizontal, + horizontal: horizontalOrDefault(this.props.horizontal), getOutermostParentListRef: this._getOutermostParentListRef, getNestedChildState: this._getNestedChildState, registerAsNestedChild: this._registerAsNestedChild, @@ -1140,6 +1165,31 @@ class VirtualizedList extends React.PureComponent { ); let ret = innerRet; + /* https://github.com/necolas/react-native-web/issues/2239: Re-enable when ScrollView.Context.Consumer is available. + if (__DEV__) { + ret = ( + + {scrollContext => { + if ( + scrollContext != null && + !scrollContext.horizontal === + !horizontalOrDefault(this.props.horizontal) && + !this._hasWarned.nesting && + this.context == null + ) { + // TODO (T46547044): use React.warn once 16.9 is sync'd: https://github.com/facebook/react/pull/15170 + console.error( + 'VirtualizedLists should never be nested inside plain ScrollViews with the same ' + + 'orientation because it can break windowing and other functionality - use another ' + + 'VirtualizedList-backed container instead.', + ); + this._hasWarned.nesting = true; + } + return innerRet; + }} + + ); + }*/ if (this.props.debug) { return ( @@ -1232,20 +1282,17 @@ class VirtualizedList extends React.PureComponent { _defaultRenderScrollComponent = props => { const onRefresh = props.onRefresh; if (this._isNestedWithSameOrientation()) { - // $FlowFixMe - Typing ReactNativeComponent revealed errors + // $FlowFixMe[prop-missing] - Typing ReactNativeComponent revealed errors return ; } else if (onRefresh) { invariant( typeof props.refreshing === 'boolean', '`refreshing` prop must be set as a boolean in order to use `onRefresh`, but got `' + - /* $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. */ - JSON.stringify(props.refreshing) + + JSON.stringify(props.refreshing ?? 'undefined') + '`', ); return ( - // $FlowFixMe Invalid prop usage + // $FlowFixMe[prop-missing] Invalid prop usage { /> ); } else { - // $FlowFixMe Invalid prop usage + // $FlowFixMe[prop-missing] Invalid prop usage return ; } }; @@ -1341,8 +1388,28 @@ class VirtualizedList extends React.PureComponent { const scrollMetrics = this._convertParentScrollMetrics( this.context.getScrollMetrics(), ); - this._scrollMetrics.visibleLength = scrollMetrics.visibleLength; - this._scrollMetrics.offset = scrollMetrics.offset; + + const metricsChanged = + this._scrollMetrics.visibleLength !== scrollMetrics.visibleLength || + this._scrollMetrics.offset !== scrollMetrics.offset; + + if (metricsChanged) { + this._scrollMetrics.visibleLength = scrollMetrics.visibleLength; + this._scrollMetrics.offset = scrollMetrics.offset; + + // If metrics of the scrollView changed, then we triggered remeasure for child list + // to ensure VirtualizedList has the right information. + this._cellKeysToChildListKeys.forEach(childListKeys => { + if (childListKeys) { + for (let childKey of childListKeys) { + const childList = this._nestedChildLists.get(childKey); + childList && + childList.ref && + childList.ref.measureLayoutRelativeToContainingList(); + } + } + }); + } }, error => { console.warn( @@ -1399,9 +1466,9 @@ class VirtualizedList extends React.PureComponent { const itemCount = this.props.getItemCount(this.props.data); for (let ii = 0; ii < itemCount; ii++) { const frame = this._getFrameMetricsApprox(ii); - /* $FlowFixMe(>=0.68.0 site=react_native_fb) This comment suppresses an - * error found when Flow v0.68 was deployed. To see the error delete this - * comment and run Flow. */ + /* $FlowFixMe[prop-missing] (>=0.68.0 site=react_native_fb) This comment + * suppresses an error found when Flow v0.68 was deployed. To see the + * error delete this comment and run Flow. */ if (frame.inLayout) { framesInLayout.push(frame); } @@ -1458,7 +1525,9 @@ class VirtualizedList extends React.PureComponent { ... }>, ): number { - return !this.props.horizontal ? metrics.height : metrics.width; + return !horizontalOrDefault(this.props.horizontal) + ? metrics.height + : metrics.width; } _selectOffset( @@ -1468,21 +1537,16 @@ class VirtualizedList extends React.PureComponent { ... }>, ): number { - return !this.props.horizontal ? metrics.y : metrics.x; + return !horizontalOrDefault(this.props.horizontal) ? metrics.y : metrics.x; } _maybeCallOnEndReached() { - const { - data, - getItemCount, - onEndReached, - onEndReachedThreshold, - } = this.props; + const {data, getItemCount, onEndReached, onEndReachedThreshold} = + this.props; const {contentLength, visibleLength, offset} = this._scrollMetrics; const distanceFromEnd = contentLength - visibleLength - offset; - const threshold = onEndReachedThreshold - ? onEndReachedThreshold * visibleLength - : 2; + const threshold = + onEndReachedThreshold != null ? onEndReachedThreshold * visibleLength : 2; if ( onEndReached && this.state.last === getItemCount(data) - 1 && @@ -1507,6 +1571,12 @@ class VirtualizedList extends React.PureComponent { this.props.initialScrollIndex > 0 && !this._hasDoneInitialScroll ) { + if (this.props.contentOffset == null) { + this.scrollToIndex({ + animated: false, + index: this.props.initialScrollIndex, + }); + } this._hasDoneInitialScroll = true; } if (this.props.onContentSizeChange) { @@ -1559,15 +1629,11 @@ class VirtualizedList extends React.PureComponent { // know our offset from our offset from our parent return; } - ({ - visibleLength, - contentLength, - offset, - dOffset, - } = this._convertParentScrollMetrics({ - visibleLength, - offset, - })); + ({visibleLength, contentLength, offset, dOffset} = + this._convertParentScrollMetrics({ + visibleLength, + offset, + })); } const dt = this._scrollMetrics.timestamp @@ -1615,11 +1681,10 @@ class VirtualizedList extends React.PureComponent { const {offset, visibleLength, velocity} = this._scrollMetrics; const itemCount = this.props.getItemCount(this.props.data); let hiPri = false; - const scrollingThreshold = - /* $FlowFixMe(>=0.63.0 site=react_native_fb) This comment suppresses an - * error found when Flow v0.63 was deployed. To see the error delete - * this comment and run Flow. */ - (this.props.onEndReachedThreshold * visibleLength) / 2; + const onEndReachedThreshold = onEndReachedThresholdOrDefault( + this.props.onEndReachedThreshold, + ); + const scrollingThreshold = (onEndReachedThreshold * visibleLength) / 2; // Mark as high priority if we're close to the start of the first item // But only if there are items before the first rendered item if (first > 0) { @@ -1700,7 +1765,14 @@ class VirtualizedList extends React.PureComponent { }; _updateCellsToRender = () => { - const {data, getItemCount, onEndReachedThreshold} = this.props; + const { + data, + getItemCount, + onEndReachedThreshold: _onEndReachedThreshold, + } = this.props; + const onEndReachedThreshold = onEndReachedThresholdOrDefault( + _onEndReachedThreshold, + ); const isVirtualizationDisabled = this._isVirtualizationDisabled(); this._updateViewableItems(data); if (!data) { @@ -1721,7 +1793,10 @@ class VirtualizedList extends React.PureComponent { // we will trust the initialScrollIndex suggestion. if (!this.props.initialScrollIndex || this._scrollMetrics.offset) { newState = computeWindowedRenderLimits( - this.props, + this.props.data, + this.props.getItemCount, + maxToRenderPerBatchOrDefault(this.props.maxToRenderPerBatch), + windowSizeOrDefault(this.props.windowSize), state, this._getFrameMetricsApprox, this._scrollMetrics, @@ -1731,11 +1806,8 @@ class VirtualizedList extends React.PureComponent { } else { const distanceFromEnd = contentLength - visibleLength - offset; const renderAhead = - /* $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. */ distanceFromEnd < onEndReachedThreshold * visibleLength - ? this.props.maxToRenderPerBatch + ? maxToRenderPerBatchOrDefault(this.props.maxToRenderPerBatch) : 0; newState = { first: 0, @@ -1766,7 +1838,8 @@ class VirtualizedList extends React.PureComponent { break; } } - if (someChildHasMore && newState) { + if (someChildHasMore) { + // $FlowFixMe[incompatible-use] The newState definitely exists past "if (newState &&" newState.last = ii; break; } @@ -1784,9 +1857,9 @@ class VirtualizedList extends React.PureComponent { }; _createViewToken = (index: number, isViewable: boolean) => { - const {data, getItem, keyExtractor} = this.props; + const {data, getItem} = this.props; const item = getItem(data, index); - return {index, item, key: keyExtractor(item, index), isViewable}; + return {index, item, key: this._keyExtractor(item, index), isViewable}; }; _getFrameMetricsApprox = ( @@ -1822,27 +1895,21 @@ class VirtualizedList extends React.PureComponent { inLayout?: boolean, ... } => { - const { - data, - getItem, - getItemCount, - getItemLayout, - keyExtractor, - } = this.props; + const {data, getItem, getItemCount, getItemLayout} = this.props; invariant( getItemCount(data) > index, 'Tried to get frame for out of range index ' + index, ); const item = getItem(data, index); - let frame = item && this._frames[keyExtractor(item, index)]; + let frame = item && this._frames[this._keyExtractor(item, index)]; if (!frame || frame.index !== index) { if (getItemLayout) { frame = getItemLayout(data, 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. */ + /* $FlowFixMe[prop-missing] (>=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. */ return frame; }; @@ -1865,7 +1932,9 @@ class VirtualizedList extends React.PureComponent { type CellRendererProps = { CellRendererComponent?: ?React.ComponentType, - ItemSeparatorComponent: ?React.ComponentType<*>, + ItemSeparatorComponent: ?React.ComponentType< + any | {highlighted: boolean, leadingItem: ?Item}, + >, cellKey: string, fillRateHelper: FillRateHelper, horizontal: ?boolean, @@ -1969,9 +2038,12 @@ class CellRenderer extends React.Component< } if (ListItemComponent) { - /* $FlowFixMe(>=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[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, @@ -2013,9 +2085,9 @@ class CellRenderer extends React.Component< ); const onLayout = - /* $FlowFixMe(>=0.68.0 site=react_native_fb) This comment suppresses an - * error found when Flow v0.68 was deployed. To see the error delete this - * comment and run Flow. */ + /* $FlowFixMe[prop-missing] (>=0.68.0 site=react_native_fb) This comment + * suppresses an error found when Flow v0.68 was deployed. To see the + * error delete this comment and run Flow. */ getItemLayout && !parentProps.debug && !fillRateHelper.enabled() ? undefined : this.props.onLayout; @@ -2032,9 +2104,9 @@ class CellRenderer extends React.Component< ? [styles.row, inversionStyle] : inversionStyle; const result = !CellRendererComponent ? ( - /* $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. */ + /* $FlowFixMe[incompatible-type-arg] (>=0.89.0 site=react_native_fb) * + This comment suppresses an error found when Flow v0.89 was deployed. * + To see the error, delete this comment and run Flow. */ {element} {itemSeparator} diff --git a/packages/react-native-web/src/vendor/react-native/VirtualizedSectionList/index.js b/packages/react-native-web/src/vendor/react-native/VirtualizedSectionList/index.js index 8d74f99c..00930728 100644 --- a/packages/react-native-web/src/vendor/react-native/VirtualizedSectionList/index.js +++ b/packages/react-native-web/src/vendor/react-native/VirtualizedSectionList/index.js @@ -1,5 +1,5 @@ /** - * Copyright (c) Facebook, Inc. and its affiliates. + * 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. @@ -10,14 +10,14 @@ 'use strict'; -import * as React from 'react'; +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 invariant from 'fbjs/lib/invariant'; -import type {ViewToken} from '../ViewabilityHelper'; - type Item = any; export type SectionBase = { @@ -99,14 +99,18 @@ type OptionalProps> = {| onEndReached?: ?({distanceFromEnd: number, ...}) => void, |}; -type VirtualizedListProps = React.ElementProps; +type VirtualizedListProps = React.ElementConfig; export type Props = {| ...RequiredProps, ...OptionalProps, ...$Diff< VirtualizedListProps, - {renderItem: $PropertyType, ...}, + { + renderItem: $PropertyType, + data: $PropertyType, + ... + }, >, |}; export type ScrollToLocationParamsType = {| @@ -117,11 +121,6 @@ export type ScrollToLocationParamsType = {| viewPosition?: number, |}; -type DefaultProps = {| - ...typeof VirtualizedList.defaultProps, - data: $ReadOnlyArray, -|}; - type State = {childProps: VirtualizedListProps, ...}; /** @@ -132,11 +131,6 @@ type State = {childProps: VirtualizedListProps, ...}; class VirtualizedSectionList< SectionT: SectionBase, > extends React.PureComponent, State> { - static defaultProps: DefaultProps = { - ...VirtualizedList.defaultProps, - data: [], - }; - scrollToLocation(params: ScrollToLocationParamsType) { let index = params.itemIndex; for (let i = 0; i < params.sectionIndex; i++) { @@ -217,11 +211,11 @@ class VirtualizedSectionList< ); } - _getItem = ( + _getItem( props: Props, sections: ?$ReadOnlyArray, index: number, - ): ?Item => { + ): ?Item { if (!sections) { return null; } @@ -243,16 +237,14 @@ class VirtualizedSectionList< } } return null; - }; + } _keyExtractor = (item: Item, index: number) => { const info = this._subExtractor(index); return (info && info.key) || String(index); }; - _subExtractor( - index: number, - ): ?{ + _subExtractor(index: number): ?{ section: SectionT, // Key of the section or combined key for section + item key: string, @@ -292,7 +284,8 @@ class VirtualizedSectionList< trailingSection: sections[i + 1], }; } else { - const extractor = section.keyExtractor || keyExtractor; + const extractor = + section.keyExtractor || keyExtractor || defaultKeyExtractor; return { section, key: @@ -313,14 +306,18 @@ class VirtualizedSectionList< if (!info) { return null; } - const keyExtractor = info.section.keyExtractor || this.props.keyExtractor; + const keyExtractorWithNullableIndex = info.section.keyExtractor; + const keyExtractorWithNonNullableIndex = + this.props.keyExtractor || defaultKeyExtractor; + const key = + keyExtractorWithNullableIndex != null + ? keyExtractorWithNullableIndex(viewable.item, info.index) + : keyExtractorWithNonNullableIndex(viewable.item, info.index ?? 0); + 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), + key, section: info.section, }; }; @@ -344,65 +341,87 @@ class VirtualizedSectionList< } }; - _renderItem = (listItemCount: number) => ({ - 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; + _renderItem = + (listItemCount: number) => + ({item, index}: {item: Item, index: number, ...}) => { + const info = this._subExtractor(index); + if (!info) { + return null; } - } else { - const renderItem = info.section.renderItem || this.props.renderItem; - const SeparatorComponent = this._getSeparatorComponent( - index, - info, - listItemCount, - ); - invariant(renderItem, 'no renderItem!'); - return ( - { - this._cellRefs[info.key] = ref; - }} - renderItem={renderItem} - section={info.section} - trailingItem={info.trailingItem} - trailingSection={info.trailingSection} - inverted={!!this.props.inverted} - /> - ); + 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, + listItemCount, + ); + invariant(renderItem, 'no renderItem!'); + return ( + + ); + } + }; + + _updatePropsFor = (cellKey, value) => { + const updateProps = this._updatePropsMap[cellKey]; + if (updateProps != null) { + updateProps(value); } }; - _onUpdateSeparator = (key: string, newProps: Object) => { - const ref = this._cellRefs[key]; - ref && ref.updateSeparatorProps(newProps); + _updateHighlightFor = (cellKey, value) => { + const updateHighlight = this._updateHighlightMap[cellKey]; + if (updateHighlight != null) { + updateHighlight(value); + } + }; + + _setUpdateHighlightFor = (cellKey, updateHighlightFn) => { + if (updateHighlightFn != null) { + this._updateHighlightMap[cellKey] = updateHighlightFn; + } else { + delete this._updateHighlightFor[cellKey]; + } + }; + + _setUpdatePropsFor = (cellKey, updatePropsFn) => { + if (updatePropsFn != null) { + this._updatePropsMap[cellKey] = updatePropsFn; + } else { + delete this._updatePropsMap[cellKey]; + } }; _getSeparatorComponent( @@ -429,7 +448,8 @@ class VirtualizedSectionList< return null; } - _cellRefs = {}; + _updateHighlightMap = {}; + _updatePropsMap = {}; _listRef: ?React.ElementRef; _captureRef = ref => { this._listRef = ref; @@ -451,134 +471,131 @@ type ItemWithSeparatorProps = $ReadOnly<{| cellKey: string, index: number, item: Item, - onUpdateSeparator: (cellKey: string, newProps: Object) => void, + setSelfHighlightCallback: ( + cellKey: string, + updateFn: ?(boolean) => void, + ) => void, + setSelfUpdatePropsCallback: ( + cellKey: string, + updateFn: ?(boolean) => void, + ) => void, prevCellKey?: ?string, + updateHighlightFor: (prevCellKey: string, value: boolean) => void, + updatePropsFor: (prevCellKey: string, value: Object) => void, renderItem: Function, inverted: boolean, |}>; -type ItemWithSeparatorState = { - separatorProps: $ReadOnly<{| - highlighted: false, - ...ItemWithSeparatorCommonProps, - |}>, - leadingSeparatorProps: $ReadOnly<{| - highlighted: false, - ...ItemWithSeparatorCommonProps, - |}>, - ... -}; +function ItemWithSeparator(props: ItemWithSeparatorProps): React.Node { + const { + LeadingSeparatorComponent, + // this is the trailing separator and is associated with this item + SeparatorComponent, + cellKey, + prevCellKey, + setSelfHighlightCallback, + updateHighlightFor, + setSelfUpdatePropsCallback, + updatePropsFor, + item, + index, + section, + inverted, + } = props; -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, - }, - }; + const [leadingSeparatorHiglighted, setLeadingSeparatorHighlighted] = + React.useState(false); - _separators = { + const [separatorHighlighted, setSeparatorHighlighted] = React.useState(false); + + const [leadingSeparatorProps, setLeadingSeparatorProps] = React.useState({ + leadingItem: props.leadingItem, + leadingSection: props.leadingSection, + section: props.section, + trailingItem: props.item, + trailingSection: props.trailingSection, + }); + const [separatorProps, setSeparatorProps] = React.useState({ + leadingItem: props.item, + leadingSection: props.leadingSection, + section: props.section, + trailingItem: props.trailingItem, + trailingSection: props.trailingSection, + }); + + React.useEffect(() => { + setSelfHighlightCallback(cellKey, setSeparatorHighlighted); + setSelfUpdatePropsCallback(cellKey, setSeparatorProps); + + return () => { + setSelfUpdatePropsCallback(cellKey, null); + setSelfHighlightCallback(cellKey, null); + }; + }, [ + cellKey, + setSelfHighlightCallback, + setSeparatorProps, + setSelfUpdatePropsCallback, + ]); + + const separators = { highlight: () => { - ['leading', 'trailing'].forEach(s => - this._separators.updateProps(s, {highlighted: true}), - ); + setLeadingSeparatorHighlighted(true); + setSeparatorHighlighted(true); + if (prevCellKey != null) { + updateHighlightFor(prevCellKey, true); + } }, unhighlight: () => { - ['leading', 'trailing'].forEach(s => - this._separators.updateProps(s, {highlighted: false}), - ); + setLeadingSeparatorHighlighted(false); + setSeparatorHighlighted(false); + if (prevCellKey != null) { + updateHighlightFor(prevCellKey, 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, - ); + updateProps: ( + select: 'leading' | 'trailing', + newProps: $Shape, + ) => { + if (select === 'leading') { + if (LeadingSeparatorComponent != null) { + setLeadingSeparatorProps({...leadingSeparatorProps, ...newProps}); + } else if (prevCellKey != null) { + // update the previous item's separator + updatePropsFor(prevCellKey, {...leadingSeparatorProps, ...newProps}); + } + } else if (select === 'trailing' && SeparatorComponent != null) { + setSeparatorProps({...separatorProps, ...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 != null && ( - - ); - const separator = SeparatorComponent != null && ( - - ); - return leadingSeparator || separator ? ( - - {inverted === false ? leadingSeparator : separator} - {element} - {inverted === false ? separator : leadingSeparator} - - ) : ( - element - ); - } + const element = props.renderItem({ + item, + index, + section, + separators, + }); + const leadingSeparator = LeadingSeparatorComponent != null && ( + + ); + const separator = SeparatorComponent != null && ( + + ); + return leadingSeparator || separator ? ( + + {inverted === false ? leadingSeparator : separator} + {element} + {inverted === false ? separator : leadingSeparator} + + ) : ( + element + ); } export default VirtualizedSectionList; \ No newline at end of file