diff --git a/Libraries/Animated/components/AnimatedFlatList.js b/Libraries/Animated/components/AnimatedFlatList.js index afe8bd88061cec..bee9dbddae7128 100644 --- a/Libraries/Animated/components/AnimatedFlatList.js +++ b/Libraries/Animated/components/AnimatedFlatList.js @@ -10,8 +10,8 @@ import type {AnimatedComponentType} from '../createAnimatedComponent'; -import FlatList from '../../Lists/FlatList'; import createAnimatedComponent from '../createAnimatedComponent'; +import FlatList from '@react-native/flat-lists/Lists/FlatList'; import * as React from 'react'; /** diff --git a/Libraries/Animated/components/AnimatedSectionList.js b/Libraries/Animated/components/AnimatedSectionList.js index 955691f6e2b278..bfb91ea50af587 100644 --- a/Libraries/Animated/components/AnimatedSectionList.js +++ b/Libraries/Animated/components/AnimatedSectionList.js @@ -10,8 +10,8 @@ import type {AnimatedComponentType} from '../createAnimatedComponent'; -import SectionList from '../../Lists/SectionList'; import createAnimatedComponent from '../createAnimatedComponent'; +import SectionList from '@react-native/flat-lists/Lists/SectionList'; import * as React from 'react'; /** diff --git a/Libraries/Inspector/NetworkOverlay.js b/Libraries/Inspector/NetworkOverlay.js index 169318cea27f28..e10915d93e6114 100644 --- a/Libraries/Inspector/NetworkOverlay.js +++ b/Libraries/Inspector/NetworkOverlay.js @@ -15,11 +15,11 @@ import type {RenderItemProps} from '../Lists/VirtualizedList'; const ScrollView = require('../Components/ScrollView/ScrollView'); const TouchableHighlight = require('../Components/Touchable/TouchableHighlight'); const View = require('../Components/View/View'); -const FlatList = require('../Lists/FlatList'); const XHRInterceptor = require('../Network/XHRInterceptor'); const StyleSheet = require('../StyleSheet/StyleSheet'); const Text = require('../Text/Text'); const WebSocketInterceptor = require('../WebSocket/WebSocketInterceptor'); +const FlatList = require('@react-native/flat-lists/Lists/FlatList'); const React = require('react'); const LISTVIEW_CELL_HEIGHT = 15; diff --git a/Libraries/Lists/FlatList.js b/Libraries/Lists/FlatList.js index 3da7714a670b29..853a158e234d1f 100644 --- a/Libraries/Lists/FlatList.js +++ b/Libraries/Lists/FlatList.js @@ -8,679 +8,8 @@ * @format */ -import typeof ScrollViewNativeComponent from '../Components/ScrollView/ScrollViewNativeComponent'; -import type {ViewStyleProp} from '../StyleSheet/StyleSheet'; -import type { - ViewabilityConfigCallbackPair, - ViewToken, -} from './ViewabilityHelper'; -import type {RenderItemProps, RenderItemType} from './VirtualizedList'; +'use strict'; -import {type ScrollResponderType} from '../Components/ScrollView/ScrollView'; -import VirtualizedList from './VirtualizedList'; -import {keyExtractor as defaultKeyExtractor} from './VirtualizeUtils'; -import memoizeOne from 'memoize-one'; +export type * from '@react-native/flat-lists/Lists/FlatList'; -const View = require('../Components/View/View'); -const StyleSheet = require('../StyleSheet/StyleSheet'); -const deepDiffer = require('../Utilities/differ/deepDiffer'); -const Platform = require('../Utilities/Platform'); -const invariant = require('invariant'); -const React = require('react'); - -type RequiredProps = {| - /** - * For simplicity, data is just a plain array. If you want to use something else, like an - * immutable list, use the underlying `VirtualizedList` directly. - */ - data: ?$ReadOnlyArray, -|}; -type OptionalProps = {| - /** - * Takes an item from `data` and renders it into the list. Example usage: - * - * ( - * - * )} - * data={[{title: 'Title Text', key: 'item1'}]} - * renderItem={({item, separators}) => ( - * this._onPress(item)} - * onShowUnderlay={separators.highlight} - * onHideUnderlay={separators.unhighlight}> - * - * {item.title} - * - * - * )} - * /> - * - * Provides additional metadata like `index` if you need it, as well as a more generic - * `separators.updateProps` function which let's you set whatever props you want to change the - * rendering of either the leading separator or trailing separator in case the more common - * `highlight` and `unhighlight` (which set the `highlighted: boolean` prop) are insufficient for - * your use-case. - */ - renderItem?: ?RenderItemType, - - /** - * Optional custom style for multi-item rows generated when numColumns > 1. - */ - columnWrapperStyle?: ViewStyleProp, - /** - * A marker property for telling the list to re-render (since it implements `PureComponent`). If - * any of your `renderItem`, Header, Footer, etc. functions depend on anything outside of the - * `data` prop, stick it here and treat it immutably. - */ - extraData?: any, - /** - * `getItemLayout` is an optional optimizations that let us skip measurement of dynamic content if - * you know the height of items a priori. `getItemLayout` is the most efficient, and is easy to - * use if you have fixed height items, for example: - * - * getItemLayout={(data, index) => ( - * {length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index} - * )} - * - * Adding `getItemLayout` can be a great performance boost for lists of several hundred items. - * Remember to include separator length (height or width) in your offset calculation if you - * specify `ItemSeparatorComponent`. - */ - getItemLayout?: ( - data: ?Array, - index: number, - ) => { - length: number, - offset: number, - index: number, - ... - }, - /** - * If true, renders items next to each other horizontally instead of stacked vertically. - */ - horizontal?: ?boolean, - /** - * How many items to render in the initial batch. This should be enough to fill the screen but not - * much more. Note these items will never be unmounted as part of the windowed rendering in order - * to improve perceived performance of scroll-to-top actions. - */ - initialNumToRender?: ?number, - /** - * Instead of starting at the top with the first item, start at `initialScrollIndex`. This - * disables the "scroll to top" optimization that keeps the first `initialNumToRender` items - * always rendered and immediately renders the items starting at this initial index. Requires - * `getItemLayout` to be implemented. - */ - initialScrollIndex?: ?number, - /** - * Reverses the direction of scroll. Uses scale transforms of -1. - */ - inverted?: ?boolean, - /** - * Used to extract a unique key for a given item at the specified index. Key is used for caching - * and as the react key to track item re-ordering. The default extractor checks `item.key`, then - * falls back to using the index, like React does. - */ - keyExtractor?: ?(item: ItemT, index: number) => string, - /** - * Multiple columns can only be rendered with `horizontal={false}` and will zig-zag like a - * `flexWrap` layout. Items should all be the same height - masonry layouts are not supported. - * - * The default value is 1. - */ - 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, - /** - * Enable an optimization to memoize the item renderer to prevent unnecessary rerenders. - */ - strictMode?: boolean, -|}; - -/** - * 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, -|}; - -type VirtualizedListProps = React.ElementConfig; - -export type Props = { - ...$Diff< - VirtualizedListProps, - { - getItem: $PropertyType, - getItemCount: $PropertyType, - getItemLayout: $PropertyType, - renderItem: $PropertyType, - keyExtractor: $PropertyType, - ... - }, - >, - ...FlatListProps, - ... -}; - -/** - * A performant interface for rendering simple, flat lists, supporting the most handy features: - * - * - Fully cross-platform. - * - Optional horizontal mode. - * - Configurable viewability callbacks. - * - Header support. - * - Footer support. - * - Separator support. - * - Pull to Refresh. - * - Scroll loading. - * - ScrollToIndex support. - * - * If you need section support, use [``](docs/sectionlist.html). - * - * Minimal Example: - * - * {item.key}} - * /> - * - * More complex, multi-select example demonstrating `PureComponent` usage for perf optimization and avoiding bugs. - * - * - By binding the `onPressItem` handler, the props will remain `===` and `PureComponent` will - * prevent wasteful re-renders unless the actual `id`, `selected`, or `title` props change, even - * if the components rendered in `MyListItem` did not have such optimizations. - * - By passing `extraData={this.state}` to `FlatList` we make sure `FlatList` itself will re-render - * when the `state.selected` changes. Without setting this prop, `FlatList` would not know it - * needs to re-render any items because it is also a `PureComponent` and the prop comparison will - * not show any changes. - * - `keyExtractor` tells the list to use the `id`s for the react keys instead of the default `key` property. - * - * - * class MyListItem extends React.PureComponent { - * _onPress = () => { - * this.props.onPressItem(this.props.id); - * }; - * - * render() { - * const textColor = this.props.selected ? "red" : "black"; - * return ( - * - * - * - * {this.props.title} - * - * - * - * ); - * } - * } - * - * class MultiSelectList extends React.PureComponent { - * state = {selected: (new Map(): Map)}; - * - * _keyExtractor = (item, index) => item.id; - * - * _onPressItem = (id: string) => { - * // updater functions are preferred for transactional updates - * this.setState((state) => { - * // copy the map rather than modifying state. - * const selected = new Map(state.selected); - * selected.set(id, !selected.get(id)); // toggle - * return {selected}; - * }); - * }; - * - * _renderItem = ({item}) => ( - * - * ); - * - * render() { - * return ( - * - * ); - * } - * } - * - * This is a convenience wrapper around [``](docs/virtualizedlist.html), - * and thus inherits its props (as well as those of `ScrollView`) that aren't explicitly listed - * here, along with the following caveats: - * - * - Internal state is not preserved when content scrolls out of the render window. Make sure all - * your data is captured in the item data or external stores like Flux, Redux, or Relay. - * - This is a `PureComponent` which means that it will not re-render if `props` remain shallow- - * equal. Make sure that everything your `renderItem` function depends on is passed as a prop - * (e.g. `extraData`) that is not `===` after updates, otherwise your UI may not update on - * changes. This includes the `data` prop and parent component state. - * - In order to constrain memory and enable smooth scrolling, content is rendered asynchronously - * offscreen. This means it's possible to scroll faster than the fill rate ands momentarily see - * blank content. This is a tradeoff that can be adjusted to suit the needs of each application, - * and we are working on improving it behind the scenes. - * - By default, the list looks for a `key` prop on each item and uses that for the React key. - * Alternatively, you can provide a custom `keyExtractor` prop. - * - * Also inherits [ScrollView Props](docs/scrollview.html#props), unless it is nested in another FlatList of same orientation. - */ -class FlatList extends React.PureComponent, void> { - props: Props; - /** - * Scrolls to the end of the content. May be janky without `getItemLayout` prop. - */ - scrollToEnd(params?: ?{animated?: ?boolean, ...}) { - if (this._listRef) { - this._listRef.scrollToEnd(params); - } - } - - /** - * Scrolls to the item at the specified index such that it is positioned in the viewable area - * such that `viewPosition` 0 places it at the top, 1 at the bottom, and 0.5 centered in the - * middle. `viewOffset` is a fixed number of pixels to offset the final target position. - * - * Note: cannot scroll to locations outside the render window without specifying the - * `getItemLayout` prop. - */ - scrollToIndex(params: { - animated?: ?boolean, - index: number, - viewOffset?: number, - viewPosition?: number, - ... - }) { - if (this._listRef) { - this._listRef.scrollToIndex(params); - } - } - - /** - * Requires linear scan through data - use `scrollToIndex` instead if possible. - * - * Note: cannot scroll to locations outside the render window without specifying the - * `getItemLayout` prop. - */ - scrollToItem(params: { - animated?: ?boolean, - item: ItemT, - viewPosition?: number, - ... - }) { - if (this._listRef) { - this._listRef.scrollToItem(params); - } - } - - /** - * Scroll to a specific content pixel offset in the list. - * - * Check out [scrollToOffset](docs/virtualizedlist.html#scrolltooffset) of VirtualizedList - */ - scrollToOffset(params: {animated?: ?boolean, offset: number, ...}) { - if (this._listRef) { - this._listRef.scrollToOffset(params); - } - } - - /** - * Tells the list an interaction has occurred, which should trigger viewability calculations, e.g. - * if `waitForInteractions` is true and the user has not scrolled. This is typically called by - * taps on items or by navigation actions. - */ - recordInteraction() { - if (this._listRef) { - this._listRef.recordInteraction(); - } - } - - /** - * Displays the scroll indicators momentarily. - * - * @platform ios - */ - flashScrollIndicators() { - if (this._listRef) { - this._listRef.flashScrollIndicators(); - } - } - - /** - * Provides a handle to the underlying scroll responder. - */ - getScrollResponder(): ?ScrollResponderType { - if (this._listRef) { - return this._listRef.getScrollResponder(); - } - } - - /** - * Provides a reference to the underlying host component - */ - getNativeScrollRef(): - | ?React.ElementRef - | ?React.ElementRef { - if (this._listRef) { - /* $FlowFixMe[incompatible-return] Suppresses errors found when fixing - * TextInput typing */ - return this._listRef.getScrollRef(); - } - } - - getScrollableNode(): any { - if (this._listRef) { - return this._listRef.getScrollableNode(); - } - } - - setNativeProps(props: {[string]: mixed, ...}) { - if (this._listRef) { - this._listRef.setNativeProps(props); - } - } - - constructor(props: Props) { - super(props); - this._checkProps(this.props); - if (this.props.viewabilityConfigCallbackPairs) { - this._virtualizedListPairs = - this.props.viewabilityConfigCallbackPairs.map(pair => ({ - viewabilityConfig: pair.viewabilityConfig, - onViewableItemsChanged: this._createOnViewableItemsChanged( - pair.onViewableItemsChanged, - ), - })); - } else if (this.props.onViewableItemsChanged) { - this._virtualizedListPairs.push({ - /* $FlowFixMe[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, - ), - }); - } - } - - // $FlowFixMe[missing-local-annot] - componentDidUpdate(prevProps: Props) { - invariant( - prevProps.numColumns === this.props.numColumns, - 'Changing numColumns on the fly is not supported. Change the key prop on FlatList when ' + - 'changing the number of columns to force a fresh render of the component.', - ); - invariant( - prevProps.onViewableItemsChanged === this.props.onViewableItemsChanged, - 'Changing onViewableItemsChanged on the fly is not supported', - ); - invariant( - !deepDiffer(prevProps.viewabilityConfig, this.props.viewabilityConfig), - 'Changing viewabilityConfig on the fly is not supported', - ); - invariant( - prevProps.viewabilityConfigCallbackPairs === - this.props.viewabilityConfigCallbackPairs, - 'Changing viewabilityConfigCallbackPairs on the fly is not supported', - ); - - this._checkProps(this.props); - } - - _listRef: ?React.ElementRef; - _virtualizedListPairs: Array = []; - - _captureRef = (ref: ?React.ElementRef) => { - this._listRef = ref; - }; - - // $FlowFixMe[missing-local-annot] - _checkProps(props: Props) { - const { - // $FlowFixMe[prop-missing] this prop doesn't exist, is only used for an invariant - getItem, - // $FlowFixMe[prop-missing] this prop doesn't exist, is only used for an invariant - getItemCount, - horizontal, - columnWrapperStyle, - onViewableItemsChanged, - viewabilityConfigCallbackPairs, - } = props; - const numColumns = numColumnsOrDefault(this.props.numColumns); - invariant( - !getItem && !getItemCount, - 'FlatList does not support custom data formats.', - ); - if (numColumns > 1) { - invariant(!horizontal, 'numColumns does not support horizontal.'); - } else { - invariant( - !columnWrapperStyle, - 'columnWrapperStyle not supported for single column lists', - ); - } - invariant( - !(onViewableItemsChanged && viewabilityConfigCallbackPairs), - 'FlatList does not support setting both onViewableItemsChanged and ' + - 'viewabilityConfigCallbackPairs.', - ); - } - - // $FlowFixMe[missing-local-annot] - _getItem = (data: Array, index: number) => { - const numColumns = numColumnsOrDefault(this.props.numColumns); - if (numColumns > 1) { - const ret = []; - for (let kk = 0; kk < numColumns; kk++) { - const itemIndex = index * numColumns + kk; - if (itemIndex < data.length) { - const item = data[itemIndex]; - ret.push(item); - } - } - return ret; - } else { - return data[index]; - } - }; - - _getItemCount = (data: ?Array): number => { - if (Array.isArray(data)) { - const numColumns = numColumnsOrDefault(this.props.numColumns); - return numColumns > 1 ? Math.ceil(data.length / numColumns) : data.length; - } else { - return 0; - } - }; - - _keyExtractor = (items: ItemT | Array, index: number): string => { - 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 - .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, v: ViewToken): void { - 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; - arr.push({...v, item, key: keyExtractor(item, index), index}); - }); - } - - _createOnViewableItemsChanged( - onViewableItemsChanged: ?(info: { - viewableItems: Array, - changed: Array, - ... - }) => void, - // $FlowFixMe[missing-local-annot] - ) { - return (info: { - viewableItems: Array, - changed: Array, - ... - }) => { - const numColumns = numColumnsOrDefault(this.props.numColumns); - if (onViewableItemsChanged) { - if (numColumns > 1) { - const changed: Array = []; - const viewableItems: Array = []; - info.viewableItems.forEach(v => - this._pushMultiColumnViewable(viewableItems, v), - ); - info.changed.forEach(v => this._pushMultiColumnViewable(changed, v)); - onViewableItemsChanged({viewableItems, changed}); - } else { - onViewableItemsChanged(info); - } - } - }; - } - - _renderer = ( - ListItemComponent: ?(React.ComponentType | React.Element), - renderItem: ?RenderItemType, - columnWrapperStyle: ?ViewStyleProp, - numColumns: ?number, - extraData: ?any, - // $FlowFixMe[missing-local-annot] - ) => { - const cols = numColumnsOrDefault(numColumns); - - const render = (props: RenderItemProps): React.Node => { - if (ListItemComponent) { - // $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] - return renderItem(props); - } else { - return null; - } - }; - - const renderProp = (info: RenderItemProps) => { - if (cols > 1) { - const {item, index} = info; - invariant( - Array.isArray(item), - 'Expected array of items with numColumns > 1', - ); - return ( - - {item.map((it, kk) => { - const element = render({ - // $FlowFixMe[incompatible-call] - item: it, - index: index * cols + kk, - separators: info.separators, - }); - return element != null ? ( - {element} - ) : null; - })} - - ); - } 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. - - ); - } -} - -const styles = StyleSheet.create({ - row: {flexDirection: 'row'}, -}); - -module.exports = FlatList; +module.exports = require('@react-native/flat-lists/Lists/FlatList'); diff --git a/index.js b/index.js index fd55a216d1fba5..b13409992b7a77 100644 --- a/index.js +++ b/index.js @@ -16,7 +16,7 @@ import typeof ActivityIndicator from './Libraries/Components/ActivityIndicator/A import typeof Button from './Libraries/Components/Button'; import typeof DatePickerIOS from './Libraries/Components/DatePicker/DatePickerIOS'; import typeof DrawerLayoutAndroid from './Libraries/Components/DrawerAndroid/DrawerLayoutAndroid'; -import typeof FlatList from './Libraries/Lists/FlatList'; +import {typeof FlatList, typeof SectionList} from '@react-native/flat-lists'; import typeof Image from './Libraries/Image/Image'; import typeof ImageBackground from './Libraries/Image/ImageBackground'; import typeof InputAccessoryView from './Libraries/Components/TextInput/InputAccessoryView'; @@ -27,7 +27,6 @@ import typeof ProgressBarAndroid from './Libraries/Components/ProgressBarAndroid import typeof RefreshControl from './Libraries/Components/RefreshControl/RefreshControl'; import typeof SafeAreaView from './Libraries/Components/SafeAreaView/SafeAreaView'; import typeof ScrollView from './Libraries/Components/ScrollView/ScrollView'; -import typeof SectionList from './Libraries/Lists/SectionList'; import typeof Slider from './Libraries/Components/Slider/Slider'; import typeof StatusBar from './Libraries/Components/StatusBar/StatusBar'; import typeof Switch from './Libraries/Components/Switch/Switch'; @@ -128,7 +127,10 @@ module.exports = { return require('./Libraries/Components/DrawerAndroid/DrawerLayoutAndroid'); }, get FlatList(): FlatList { - return require('./Libraries/Lists/FlatList'); + return require('@react-native/flat-lists/Lists/FlatList'); + }, + get SectionList(): SectionList { + return require('@react-native/flat-lists/Lists/SectionList').default; }, get Image(): Image { return require('./Libraries/Image/Image'); @@ -168,9 +170,6 @@ module.exports = { get ScrollView(): ScrollView { return require('./Libraries/Components/ScrollView/ScrollView'); }, - get SectionList(): SectionList { - return require('./Libraries/Lists/SectionList').default; - }, get Slider(): Slider { warnOnce( 'slider-moved', diff --git a/package.json b/package.json index efc43ed0877e67..124d2092581037 100644 --- a/package.json +++ b/package.json @@ -115,6 +115,7 @@ "@react-native/assets": "1.0.0", "@react-native/normalize-color": "2.1.0", "@react-native/polyfills": "2.0.0", + "@react-native/flat-lists": "0.72.0", "abort-controller": "^3.0.0", "anser": "^1.4.9", "base64-js": "^1.1.2", diff --git a/Libraries/Lists/FlatList.d.ts b/packages/flat-lists/Lists/FlatList.d.ts similarity index 97% rename from Libraries/Lists/FlatList.d.ts rename to packages/flat-lists/Lists/FlatList.d.ts index f09464025a0c0a..c65e4d15e81ae7 100644 --- a/Libraries/Lists/FlatList.d.ts +++ b/packages/flat-lists/Lists/FlatList.d.ts @@ -12,11 +12,10 @@ import type { ListRenderItem, ViewToken, VirtualizedListProps, -} from './VirtualizedList'; -import type {ScrollViewComponent} from '../Components/ScrollView/ScrollView'; -import {StyleProp} from '../StyleSheet/StyleSheet'; -import {ViewStyle} from '../StyleSheet/StyleSheetTypes'; -import {View} from '../Components/View/View'; +} from '@react-native/virtualized-lists'; +import type {ScrollViewComponent} from 'react-native/Libraries/Components/ScrollView/ScrollView'; + +import {StyleProp, ViewStyle, View} from 'react-native'; export interface FlatListProps extends VirtualizedListProps { /** diff --git a/packages/flat-lists/Lists/FlatList.js b/packages/flat-lists/Lists/FlatList.js new file mode 100644 index 00000000000000..48e2fa7c65fda8 --- /dev/null +++ b/packages/flat-lists/Lists/FlatList.js @@ -0,0 +1,687 @@ +/** + * 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 * as React from 'react'; +import typeof ScrollViewNativeComponent from 'react-native'; +import type {ViewStyleProp} from 'react-native/Libraries/StyleSheet/StyleSheet'; +import type { + RenderItemProps, + RenderItemType, + ViewabilityConfigCallbackPair, + ViewToken, +} from '@react-native/virtualized-lists'; +import {type ScrollResponderType} from 'react-native/Libraries/Components/ScrollView/ScrollView'; + +import { + VirtualizedList, + keyExtractor as defaultKeyExtractor, +} from '@react-native/virtualized-lists'; +import memoizeOne from 'memoize-one'; +import {View, StyleSheet, Platform} from 'react-native'; + +const deepDiffer = require('react-native/Libraries/Utilities/differ/deepDiffer'); +const invariant = require('invariant'); + +type RequiredProps = {| + /** + * For simplicity, data is just a plain array. If you want to use something else, like an + * immutable list, use the underlying `VirtualizedList` directly. + */ + data: ?$ReadOnlyArray, +|}; + +type OptionalProps = {| + /** + * Takes an item from `data` and renders it into the list. Example usage: + * + * ( + * + * )} + * data={[{title: 'Title Text', key: 'item1'}]} + * renderItem={({item, separators}) => ( + * this._onPress(item)} + * onShowUnderlay={separators.highlight} + * onHideUnderlay={separators.unhighlight}> + * + * {item.title} + * + * + * )} + * /> + * + * Provides additional metadata like `index` if you need it, as well as a more generic + * `separators.updateProps` function which let's you set whatever props you want to change the + * rendering of either the leading separator or trailing separator in case the more common + * `highlight` and `unhighlight` (which set the `highlighted: boolean` prop) are insufficient for + * your use-case. + */ + renderItem?: ?RenderItemType, + + /** + * Optional custom style for multi-item rows generated when numColumns > 1. + */ + columnWrapperStyle?: ViewStyleProp, + /** + * A marker property for telling the list to re-render (since it implements `PureComponent`). If + * any of your `renderItem`, Header, Footer, etc. functions depend on anything outside of the + * `data` prop, stick it here and treat it immutably. + */ + extraData?: any, + /** + * `getItemLayout` is an optional optimizations that let us skip measurement of dynamic content if + * you know the height of items a priori. `getItemLayout` is the most efficient, and is easy to + * use if you have fixed height items, for example: + * + * getItemLayout={(data, index) => ( + * {length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index} + * )} + * + * Adding `getItemLayout` can be a great performance boost for lists of several hundred items. + * Remember to include separator length (height or width) in your offset calculation if you + * specify `ItemSeparatorComponent`. + */ + getItemLayout?: ( + data: ?Array, + index: number, + ) => { + length: number, + offset: number, + index: number, + ... + }, + /** + * If true, renders items next to each other horizontally instead of stacked vertically. + */ + horizontal?: ?boolean, + /** + * How many items to render in the initial batch. This should be enough to fill the screen but not + * much more. Note these items will never be unmounted as part of the windowed rendering in order + * to improve perceived performance of scroll-to-top actions. + */ + initialNumToRender?: ?number, + /** + * Instead of starting at the top with the first item, start at `initialScrollIndex`. This + * disables the "scroll to top" optimization that keeps the first `initialNumToRender` items + * always rendered and immediately renders the items starting at this initial index. Requires + * `getItemLayout` to be implemented. + */ + initialScrollIndex?: ?number, + /** + * Reverses the direction of scroll. Uses scale transforms of -1. + */ + inverted?: ?boolean, + /** + * Used to extract a unique key for a given item at the specified index. Key is used for caching + * and as the react key to track item re-ordering. The default extractor checks `item.key`, then + * falls back to using the index, like React does. + */ + keyExtractor?: ?(item: ItemT, index: number) => string, + /** + * Multiple columns can only be rendered with `horizontal={false}` and will zig-zag like a + * `flexWrap` layout. Items should all be the same height - masonry layouts are not supported. + * + * The default value is 1. + */ + 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, + /** + * Enable an optimization to memoize the item renderer to prevent unnecessary rerenders. + */ + strictMode?: boolean, +|}; + +/** + * 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, +|}; + +type VirtualizedListProps = React.ElementConfig; + +export type Props = { + ...$Diff< + VirtualizedListProps, + { + getItem: $PropertyType, + getItemCount: $PropertyType, + getItemLayout: $PropertyType, + renderItem: $PropertyType, + keyExtractor: $PropertyType, + ... + }, + >, + ...FlatListProps, + ... +}; + +/** + * A performant interface for rendering simple, flat lists, supporting the most handy features: + * + * - Fully cross-platform. + * - Optional horizontal mode. + * - Configurable viewability callbacks. + * - Header support. + * - Footer support. + * - Separator support. + * - Pull to Refresh. + * - Scroll loading. + * - ScrollToIndex support. + * + * If you need section support, use [``](docs/sectionlist.html). + * + * Minimal Example: + * + * {item.key}} + * /> + * + * More complex, multi-select example demonstrating `PureComponent` usage for perf optimization and avoiding bugs. + * + * - By binding the `onPressItem` handler, the props will remain `===` and `PureComponent` will + * prevent wasteful re-renders unless the actual `id`, `selected`, or `title` props change, even + * if the components rendered in `MyListItem` did not have such optimizations. + * - By passing `extraData={this.state}` to `FlatList` we make sure `FlatList` itself will re-render + * when the `state.selected` changes. Without setting this prop, `FlatList` would not know it + * needs to re-render any items because it is also a `PureComponent` and the prop comparison will + * not show any changes. + * - `keyExtractor` tells the list to use the `id`s for the react keys instead of the default `key` property. + * + * + * class MyListItem extends React.PureComponent { + * _onPress = () => { + * this.props.onPressItem(this.props.id); + * }; + * + * render() { + * const textColor = this.props.selected ? "red" : "black"; + * return ( + * + * + * + * {this.props.title} + * + * + * + * ); + * } + * } + * + * class MultiSelectList extends React.PureComponent { + * state = {selected: (new Map(): Map)}; + * + * _keyExtractor = (item, index) => item.id; + * + * _onPressItem = (id: string) => { + * // updater functions are preferred for transactional updates + * this.setState((state) => { + * // copy the map rather than modifying state. + * const selected = new Map(state.selected); + * selected.set(id, !selected.get(id)); // toggle + * return {selected}; + * }); + * }; + * + * _renderItem = ({item}) => ( + * + * ); + * + * render() { + * return ( + * + * ); + * } + * } + * + * This is a convenience wrapper around [``](docs/virtualizedlist.html), + * and thus inherits its props (as well as those of `ScrollView`) that aren't explicitly listed + * here, along with the following caveats: + * + * - Internal state is not preserved when content scrolls out of the render window. Make sure all + * your data is captured in the item data or external stores like Flux, Redux, or Relay. + * - This is a `PureComponent` which means that it will not re-render if `props` remain shallow- + * equal. Make sure that everything your `renderItem` function depends on is passed as a prop + * (e.g. `extraData`) that is not `===` after updates, otherwise your UI may not update on + * changes. This includes the `data` prop and parent component state. + * - In order to constrain memory and enable smooth scrolling, content is rendered asynchronously + * offscreen. This means it's possible to scroll faster than the fill rate ands momentarily see + * blank content. This is a tradeoff that can be adjusted to suit the needs of each application, + * and we are working on improving it behind the scenes. + * - By default, the list looks for a `key` prop on each item and uses that for the React key. + * Alternatively, you can provide a custom `keyExtractor` prop. + * + * Also inherits [ScrollView Props](docs/scrollview.html#props), unless it is nested in another FlatList of same orientation. + */ +class FlatList extends React.PureComponent, void> { + props: Props; + /** + * Scrolls to the end of the content. May be janky without `getItemLayout` prop. + */ + scrollToEnd(params?: ?{animated?: ?boolean, ...}) { + if (this._listRef) { + this._listRef.scrollToEnd(params); + } + } + + /** + * Scrolls to the item at the specified index such that it is positioned in the viewable area + * such that `viewPosition` 0 places it at the top, 1 at the bottom, and 0.5 centered in the + * middle. `viewOffset` is a fixed number of pixels to offset the final target position. + * + * Note: cannot scroll to locations outside the render window without specifying the + * `getItemLayout` prop. + */ + scrollToIndex(params: { + animated?: ?boolean, + index: number, + viewOffset?: number, + viewPosition?: number, + ... + }) { + if (this._listRef) { + this._listRef.scrollToIndex(params); + } + } + + /** + * Requires linear scan through data - use `scrollToIndex` instead if possible. + * + * Note: cannot scroll to locations outside the render window without specifying the + * `getItemLayout` prop. + */ + scrollToItem(params: { + animated?: ?boolean, + item: ItemT, + viewPosition?: number, + ... + }) { + if (this._listRef) { + this._listRef.scrollToItem(params); + } + } + + /** + * Scroll to a specific content pixel offset in the list. + * + * Check out [scrollToOffset](docs/virtualizedlist.html#scrolltooffset) of VirtualizedList + */ + scrollToOffset(params: {animated?: ?boolean, offset: number, ...}) { + if (this._listRef) { + this._listRef.scrollToOffset(params); + } + } + + /** + * Tells the list an interaction has occurred, which should trigger viewability calculations, e.g. + * if `waitForInteractions` is true and the user has not scrolled. This is typically called by + * taps on items or by navigation actions. + */ + recordInteraction() { + if (this._listRef) { + this._listRef.recordInteraction(); + } + } + + /** + * Displays the scroll indicators momentarily. + * + * @platform ios + */ + flashScrollIndicators() { + if (this._listRef) { + this._listRef.flashScrollIndicators(); + } + } + + /** + * Provides a handle to the underlying scroll responder. + */ + getScrollResponder(): ?ScrollResponderType { + if (this._listRef) { + return this._listRef.getScrollResponder(); + } + } + + /** + * Provides a reference to the underlying host component + */ + getNativeScrollRef(): + | ?React.ElementRef + | ?React.ElementRef { + if (this._listRef) { + /* $FlowFixMe[incompatible-return] Suppresses errors found when fixing + * TextInput typing */ + return this._listRef.getScrollRef(); + } + } + + getScrollableNode(): any { + if (this._listRef) { + return this._listRef.getScrollableNode(); + } + } + + setNativeProps(props: {[string]: mixed, ...}) { + if (this._listRef) { + this._listRef.setNativeProps(props); + } + } + + constructor(props: Props) { + super(props); + this._checkProps(this.props); + if (this.props.viewabilityConfigCallbackPairs) { + this._virtualizedListPairs = + this.props.viewabilityConfigCallbackPairs.map(pair => ({ + viewabilityConfig: pair.viewabilityConfig, + onViewableItemsChanged: this._createOnViewableItemsChanged( + pair.onViewableItemsChanged, + ), + })); + } else if (this.props.onViewableItemsChanged) { + this._virtualizedListPairs.push({ + /* $FlowFixMe[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, + ), + }); + } + } + + // $FlowFixMe[missing-local-annot] + componentDidUpdate(prevProps: Props) { + invariant( + prevProps.numColumns === this.props.numColumns, + 'Changing numColumns on the fly is not supported. Change the key prop on FlatList when ' + + 'changing the number of columns to force a fresh render of the component.', + ); + invariant( + prevProps.onViewableItemsChanged === this.props.onViewableItemsChanged, + 'Changing onViewableItemsChanged on the fly is not supported', + ); + invariant( + !deepDiffer(prevProps.viewabilityConfig, this.props.viewabilityConfig), + 'Changing viewabilityConfig on the fly is not supported', + ); + invariant( + prevProps.viewabilityConfigCallbackPairs === + this.props.viewabilityConfigCallbackPairs, + 'Changing viewabilityConfigCallbackPairs on the fly is not supported', + ); + + this._checkProps(this.props); + } + + _listRef: ?React.ElementRef; + _virtualizedListPairs: Array = []; + + _captureRef = (ref: ?React.ElementRef) => { + this._listRef = ref; + }; + + // $FlowFixMe[missing-local-annot] + _checkProps(props: Props) { + const { + // $FlowFixMe[prop-missing] this prop doesn't exist, is only used for an invariant + getItem, + // $FlowFixMe[prop-missing] this prop doesn't exist, is only used for an invariant + getItemCount, + horizontal, + columnWrapperStyle, + onViewableItemsChanged, + viewabilityConfigCallbackPairs, + } = props; + const numColumns = numColumnsOrDefault(this.props.numColumns); + invariant( + !getItem && !getItemCount, + 'FlatList does not support custom data formats.', + ); + if (numColumns > 1) { + invariant(!horizontal, 'numColumns does not support horizontal.'); + } else { + invariant( + !columnWrapperStyle, + 'columnWrapperStyle not supported for single column lists', + ); + } + invariant( + !(onViewableItemsChanged && viewabilityConfigCallbackPairs), + 'FlatList does not support setting both onViewableItemsChanged and ' + + 'viewabilityConfigCallbackPairs.', + ); + } + + // $FlowFixMe[missing-local-annot] + _getItem = (data: Array, index: number) => { + const numColumns = numColumnsOrDefault(this.props.numColumns); + if (numColumns > 1) { + const ret = []; + for (let kk = 0; kk < numColumns; kk++) { + const itemIndex = index * numColumns + kk; + if (itemIndex < data.length) { + const item = data[itemIndex]; + ret.push(item); + } + } + return ret; + } else { + return data[index]; + } + }; + + _getItemCount = (data: ?Array): number => { + if (Array.isArray(data)) { + const numColumns = numColumnsOrDefault(this.props.numColumns); + return numColumns > 1 ? Math.ceil(data.length / numColumns) : data.length; + } else { + return 0; + } + }; + + _keyExtractor = (items: ItemT | Array, index: number): string => { + 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 + .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, v: ViewToken): void { + 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; + arr.push({...v, item, key: keyExtractor(item, index), index}); + }); + } + + _createOnViewableItemsChanged( + onViewableItemsChanged: ?(info: { + viewableItems: Array, + changed: Array, + ... + }) => void, + // $FlowFixMe[missing-local-annot] + ) { + return (info: { + viewableItems: Array, + changed: Array, + ... + }) => { + const numColumns = numColumnsOrDefault(this.props.numColumns); + if (onViewableItemsChanged) { + if (numColumns > 1) { + const changed: Array = []; + const viewableItems: Array = []; + info.viewableItems.forEach(v => + this._pushMultiColumnViewable(viewableItems, v), + ); + info.changed.forEach(v => this._pushMultiColumnViewable(changed, v)); + onViewableItemsChanged({viewableItems, changed}); + } else { + onViewableItemsChanged(info); + } + } + }; + } + + _renderer = ( + ListItemComponent: ?(React.ComponentType | React.Element), + renderItem: ?RenderItemType, + columnWrapperStyle: ?ViewStyleProp, + numColumns: ?number, + extraData: ?any, + // $FlowFixMe[missing-local-annot] + ) => { + const cols = numColumnsOrDefault(numColumns); + + const render = (props: RenderItemProps): React.Node => { + if (ListItemComponent) { + // $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] + return renderItem(props); + } else { + return null; + } + }; + + const renderProp = (info: RenderItemProps) => { + if (cols > 1) { + const {item, index} = info; + invariant( + Array.isArray(item), + 'Expected array of items with numColumns > 1', + ); + return ( + + {item.map((it, kk) => { + const element = render({ + // $FlowFixMe[incompatible-call] + item: it, + index: index * cols + kk, + separators: info.separators, + }); + return element != null ? ( + {element} + ) : null; + })} + + ); + } 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. + + ); + } +} + +const styles = StyleSheet.create({ + row: {flexDirection: 'row'}, +}); + +module.exports = FlatList; diff --git a/Libraries/Lists/FlatList.js.flow b/packages/flat-lists/Lists/FlatList.js.flow similarity index 83% rename from Libraries/Lists/FlatList.js.flow rename to packages/flat-lists/Lists/FlatList.js.flow index 4a0ee0be1d7269..23fc0f3fdb5514 100644 --- a/Libraries/Lists/FlatList.js.flow +++ b/packages/flat-lists/Lists/FlatList.js.flow @@ -9,17 +9,18 @@ */ const React = require('react'); -const View = require('../Components/View/View'); +const View = require('react-native/Libraries/Components/View/View'); -import typeof ScrollViewNativeComponent from '../Components/ScrollView/ScrollViewNativeComponent'; -import {type ScrollResponderType} from '../Components/ScrollView/ScrollView'; -import type {ViewStyleProp} from '../StyleSheet/StyleSheet'; +import typeof ScrollViewNativeComponent from 'react-native/Libraries/Components/ScrollView/ScrollViewNativeComponent'; +import {type ScrollResponderType} from 'react-native/Libraries/Components/ScrollView/ScrollView'; +import type {ViewStyleProp} from 'react-native/Libraries/StyleSheet/StyleSheet'; import type { + RenderItemType, + RenderItemProps, ViewToken, ViewabilityConfigCallbackPair, -} from './ViewabilityHelper'; -import type {RenderItemType, RenderItemProps} from './VirtualizedList'; -import typeof VirtualizedList from './VirtualizedList'; +} from '@react-native/virtualized-lists'; +import typeof VirtualizedList from '@react-native/virtualized-lists/List/VirtualizedList'; type RequiredProps = {| /** diff --git a/Libraries/Lists/SectionList.d.ts b/packages/flat-lists/Lists/SectionList.d.ts similarity index 97% rename from Libraries/Lists/SectionList.d.ts rename to packages/flat-lists/Lists/SectionList.d.ts index 48c24b1a60f9d2..ea832024c393c0 100644 --- a/Libraries/Lists/SectionList.d.ts +++ b/packages/flat-lists/Lists/SectionList.d.ts @@ -11,14 +11,12 @@ import type * as React from 'react'; import type { ListRenderItemInfo, VirtualizedListWithoutRenderItemProps, -} from './VirtualizedList'; +} from '@react-native/virtualized-lists'; import type { ScrollView, ScrollViewProps, -} from '../Components/ScrollView/ScrollView'; -import {NodeHandle} from '../ReactNative/RendererProxy'; -import {StyleProp} from '../StyleSheet/StyleSheet'; -import {ViewStyle} from '../StyleSheet/StyleSheetTypes'; +} from 'react-native/Libraries/Components/ScrollView/ScrollView'; +import {NodeHandle, StyleProp, ViewStyle} from 'react-native'; /** * @see https://reactnative.dev/docs/sectionlist diff --git a/Libraries/Lists/SectionList.js b/packages/flat-lists/Lists/SectionList.js similarity index 96% rename from Libraries/Lists/SectionList.js rename to packages/flat-lists/Lists/SectionList.js index 3520b31eca9829..40213bcefc6ce2 100644 --- a/Libraries/Lists/SectionList.js +++ b/packages/flat-lists/Lists/SectionList.js @@ -9,17 +9,16 @@ */ 'use strict'; - -import type {ScrollResponderType} from '../Components/ScrollView/ScrollView'; +import * as React from 'react'; +import type {ScrollResponderType} from 'react-native/Libraries/Components/ScrollView/ScrollView'; import type { - Props as VirtualizedSectionListProps, + VirtualizedSectionListProps, ScrollToLocationParamsType, SectionBase as _SectionBase, -} from './VirtualizedSectionList'; +} from '@react-native/virtualized-list'; -import Platform from '../Utilities/Platform'; -import VirtualizedSectionList from './VirtualizedSectionList'; -import * as React from 'react'; +import Platform from 'react-native/Libraries/Utilities/Platform'; +import {VirtualizedSectionList} from '@react-native/virtualized-list'; type Item = any; diff --git a/Libraries/Lists/SectionListModern.js b/packages/flat-lists/Lists/SectionListModern.js similarity index 96% rename from Libraries/Lists/SectionListModern.js rename to packages/flat-lists/Lists/SectionListModern.js index c7856e13d9a666..fbb088ff02228e 100644 --- a/Libraries/Lists/SectionListModern.js +++ b/packages/flat-lists/Lists/SectionListModern.js @@ -10,17 +10,18 @@ 'use strict'; -import type {ScrollResponderType} from '../Components/ScrollView/ScrollView'; +import React, {forwardRef, useImperativeHandle, useRef} from 'react'; + +import type {AbstractComponent, Element, ElementRef} from 'react'; +import type {ScrollResponderType} from 'react-native/Libraries/Components/ScrollView/ScrollView'; import type { - Props as VirtualizedSectionListProps, + VirtualizedSectionListProps, ScrollToLocationParamsType, SectionBase as _SectionBase, -} from './VirtualizedSectionList'; -import type {AbstractComponent, Element, ElementRef} from 'react'; +} from '@react-native/virtualized-list'; -import Platform from '../Utilities/Platform'; -import VirtualizedSectionList from './VirtualizedSectionList'; -import React, {forwardRef, useImperativeHandle, useRef} from 'react'; +import Platform from 'react-native/Libraries/Utilities/Platform'; +import {VirtualizedSectionList} from '@react-native/virtualized-list'; type Item = any; diff --git a/Libraries/Lists/__flowtests__/FlatList-flowtest.js b/packages/flat-lists/Lists/__flowtests__/FlatList-flowtest.js similarity index 100% rename from Libraries/Lists/__flowtests__/FlatList-flowtest.js rename to packages/flat-lists/Lists/__flowtests__/FlatList-flowtest.js index fb62a18ca4f430..0935b1a2cf1587 100644 --- a/Libraries/Lists/__flowtests__/FlatList-flowtest.js +++ b/packages/flat-lists/Lists/__flowtests__/FlatList-flowtest.js @@ -10,8 +10,8 @@ 'use strict'; -const FlatList = require('../FlatList'); const React = require('react'); +const FlatList = require('../FlatList'); function renderMyListItem(info: { item: {title: string, ...}, diff --git a/Libraries/Lists/__flowtests__/SectionList-flowtest.js b/packages/flat-lists/Lists/__flowtests__/SectionList-flowtest.js similarity index 100% rename from Libraries/Lists/__flowtests__/SectionList-flowtest.js rename to packages/flat-lists/Lists/__flowtests__/SectionList-flowtest.js index 9f878d89b0607d..0860556f489b29 100644 --- a/Libraries/Lists/__flowtests__/SectionList-flowtest.js +++ b/packages/flat-lists/Lists/__flowtests__/SectionList-flowtest.js @@ -10,8 +10,8 @@ 'use strict'; -import SectionList from '../SectionList'; import * as React from 'react'; +import SectionList from '../SectionList'; function renderMyListItem(info: { item: {title: string, ...}, diff --git a/Libraries/Lists/__tests__/FlatList-test.js b/packages/flat-lists/Lists/__tests__/FlatList-test.js similarity index 97% rename from Libraries/Lists/__tests__/FlatList-test.js rename to packages/flat-lists/Lists/__tests__/FlatList-test.js index bc7c9d1faf16cc..7d3f8d15993244 100644 --- a/Libraries/Lists/__tests__/FlatList-test.js +++ b/packages/flat-lists/Lists/__tests__/FlatList-test.js @@ -10,8 +10,8 @@ 'use strict'; -const FlatList = require('../FlatList'); const React = require('react'); +const FlatList = require('../FlatList'); const ReactTestRenderer = require('react-test-renderer'); describe('FlatList', () => { @@ -94,7 +94,7 @@ describe('FlatList', () => { }); it('getNativeScrollRef for case where it returns a native view', () => { jest.resetModules(); - jest.unmock('../../Components/ScrollView/ScrollView'); + jest.unmock('react-native/Libraries/Components/ScrollView/ScrollView'); const listRef = React.createRef(null); @@ -127,7 +127,7 @@ describe('FlatList', () => { it('getNativeScrollRef for case where it returns a native scroll view', () => { jest.resetModules(); - jest.unmock('../../Components/ScrollView/ScrollView'); + jest.unmock('react-native/Libraries/Components/ScrollView/ScrollView'); function ListItemComponent({item}) { return ; diff --git a/Libraries/Lists/__tests__/SectionList-test.js b/packages/flat-lists/Lists/__tests__/SectionList-test.js similarity index 100% rename from Libraries/Lists/__tests__/SectionList-test.js rename to packages/flat-lists/Lists/__tests__/SectionList-test.js index 9eb6e340e30d16..2064f2e50f7e43 100644 --- a/Libraries/Lists/__tests__/SectionList-test.js +++ b/packages/flat-lists/Lists/__tests__/SectionList-test.js @@ -10,8 +10,8 @@ 'use strict'; -import SectionList from '../SectionList'; import * as React from 'react'; +import SectionList from '../SectionList'; import ReactTestRenderer from 'react-test-renderer'; describe('SectionList', () => { diff --git a/Libraries/Lists/__tests__/__snapshots__/FlatList-test.js.snap b/packages/flat-lists/Lists/__tests__/__snapshots__/FlatList-test.js.snap similarity index 100% rename from Libraries/Lists/__tests__/__snapshots__/FlatList-test.js.snap rename to packages/flat-lists/Lists/__tests__/__snapshots__/FlatList-test.js.snap diff --git a/Libraries/Lists/__tests__/__snapshots__/SectionList-test.js.snap b/packages/flat-lists/Lists/__tests__/__snapshots__/SectionList-test.js.snap similarity index 100% rename from Libraries/Lists/__tests__/__snapshots__/SectionList-test.js.snap rename to packages/flat-lists/Lists/__tests__/__snapshots__/SectionList-test.js.snap diff --git a/packages/flat-lists/index.d.ts b/packages/flat-lists/index.d.ts new file mode 100644 index 00000000000000..7c3818a661781f --- /dev/null +++ b/packages/flat-lists/index.d.ts @@ -0,0 +1,10 @@ +/** + * 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 + */ + +export * from './Lists/FlatList'; diff --git a/packages/flat-lists/index.js b/packages/flat-lists/index.js new file mode 100644 index 00000000000000..972dec83d4bb92 --- /dev/null +++ b/packages/flat-lists/index.js @@ -0,0 +1,46 @@ +/** + * 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 + */ + +'use strict'; + +import typeof FlatList from './Lists/FlatList'; +import typeof SectionList from './Lists/SectionList'; + +import type {Props as FlatListProps} from './Lists/FlatList'; +import type { + Props as SectionListProps, + SectionListRenderItem, + SectionListRenderItemInfo, + SectionListScrollParams, + SectionListData, + SectionBase, +} from './Lists/SectionList'; + +export type { + FlatListProps, + SectionListProps, + SectionListRenderItem, + SectionListRenderItemInfo, + SectionListScrollParams, + SectionListData, + SectionBase, +}; + +module.exports = { + get FlatList(): FlatList { + return require('./Lists/FlatList'); + }, + get SectionList(): SectionList { + return require('./Lists/SectionList').default; + }, + get SectionListModern(): $FlowFixMe { + return require('./Lists/SectionListModern'); + }, +}; diff --git a/packages/flat-lists/package.json b/packages/flat-lists/package.json new file mode 100644 index 00000000000000..a1b523448bdaaf --- /dev/null +++ b/packages/flat-lists/package.json @@ -0,0 +1,15 @@ +{ + "name": "@react-native/flat-lists", + "version": "0.72.0", + "description": "Flat lists for React Native.", + "repository": { + "type": "git", + "url": "git@github.com:facebook/react-native.git", + "directory": "packages/flat-lists" + }, + "license": "MIT", + "peerDependencies": { + "react-native": "*", + "@react-native/virtualized-lists": "1.0.0" + } + } diff --git a/packages/rn-tester/js/examples/FlatList/FlatList-basic.js b/packages/rn-tester/js/examples/FlatList/FlatList-basic.js index 64f2800a355451..706c6136c64344 100644 --- a/packages/rn-tester/js/examples/FlatList/FlatList-basic.js +++ b/packages/rn-tester/js/examples/FlatList/FlatList-basic.js @@ -11,7 +11,7 @@ 'use strict'; import type {AnimatedComponentType} from 'react-native/Libraries/Animated/createAnimatedComponent'; -import typeof FlatListType from 'react-native/Libraries/Lists/FlatList'; +import typeof FlatListType from '@react-native/flat-lists/Lists/FlatList'; import type {RenderItemProps} from 'react-native/Libraries/Lists/VirtualizedListProps'; import type {RNTesterModuleExample} from '../../types/RNTesterTypes'; diff --git a/packages/rn-tester/js/examples/FlatList/FlatList-nested.js b/packages/rn-tester/js/examples/FlatList/FlatList-nested.js index f2d1282a5a72b1..29888163c1b680 100644 --- a/packages/rn-tester/js/examples/FlatList/FlatList-nested.js +++ b/packages/rn-tester/js/examples/FlatList/FlatList-nested.js @@ -16,7 +16,7 @@ import {FlatList, StyleSheet, Text, View} from 'react-native'; import RNTesterPage from '../../components/RNTesterPage'; import type {RNTesterModuleExample} from '../../types/RNTesterTypes'; -import type {ViewToken} from '../../../../../Libraries/Lists/ViewabilityHelper'; +import type {ViewToken} from 'react-native/Libraries/Lists/ViewabilityHelper'; type OuterItem = 'head' | 'vertical' | 'horizontal' | 'filler'; diff --git a/packages/rn-tester/js/examples/FlatList/FlatList-onViewableItemsChanged.js b/packages/rn-tester/js/examples/FlatList/FlatList-onViewableItemsChanged.js index 76e29fac105fe1..f65111e16596ba 100644 --- a/packages/rn-tester/js/examples/FlatList/FlatList-onViewableItemsChanged.js +++ b/packages/rn-tester/js/examples/FlatList/FlatList-onViewableItemsChanged.js @@ -10,7 +10,7 @@ 'use strict'; -import type {ViewToken} from '../../../../../Libraries/Lists/ViewabilityHelper'; +import type {ViewToken} from 'react-native/Libraries/Lists/ViewabilityHelper'; import type {RNTesterModuleExample} from '../../types/RNTesterTypes'; import BaseFlatListExample from './BaseFlatListExample'; diff --git a/types/index.d.ts b/types/index.d.ts index 6ef79292429588..216cc13f0ae228 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -115,8 +115,8 @@ export * from '../Libraries/Interaction/InteractionManager'; export * from '../Libraries/Interaction/PanResponder'; export * from '../Libraries/LayoutAnimation/LayoutAnimation'; export * from '../Libraries/Linking/Linking'; -export * from '../Libraries/Lists/FlatList'; -export * from '../Libraries/Lists/SectionList'; +export * from '@react-native/flat-lists/Lists/FlatList'; +export * from '@react-native/flat-lists/Lists/SectionList'; export * from '../Libraries/Lists/VirtualizedList'; export * from '../Libraries/LogBox/LogBox'; export * from '../Libraries/Modal/Modal'; diff --git a/types/public/DeprecatedPropertiesAlias.d.ts b/types/public/DeprecatedPropertiesAlias.d.ts index 55c924ceaf4bb6..5cd57c75ca1813 100644 --- a/types/public/DeprecatedPropertiesAlias.d.ts +++ b/types/public/DeprecatedPropertiesAlias.d.ts @@ -41,8 +41,8 @@ import { ImagePropsAndroid, ImageBackgroundProps, FlatListProps, - VirtualizedListProps, SectionListProps, + VirtualizedListProps, ModalProps, TouchableWithoutFeedbackProps, TouchableHighlightProps,