/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow * @format */ import type {ViewStyleProp} from 'react-native/Libraries/StyleSheet/StyleSheet'; import type { FocusEvent, LayoutEvent, } from 'react-native/Libraries/Types/CoreEventTypes'; import type {CellRendererProps, RenderItemType} from './VirtualizedListProps'; import {View, StyleSheet} from 'react-native'; import {VirtualizedListCellContextProvider} from './VirtualizedListContext.js'; import invariant from 'invariant'; import * as React from 'react'; export type Props = { CellRendererComponent?: ?React.ComponentType>, ItemSeparatorComponent: ?React.ComponentType< any | {highlighted: boolean, leadingItem: ?ItemT}, >, ListItemComponent?: ?(React.ComponentType | React.Element), cellKey: string, horizontal: ?boolean, index: number, inversionStyle: ViewStyleProp, item: ItemT, onCellLayout?: (event: LayoutEvent, cellKey: string, index: number) => void, onCellFocusCapture?: (event: FocusEvent) => void, onUnmount: (cellKey: string) => void, onUpdateSeparators: ( cellKeys: Array, props: $Shape>, ) => void, prevCellKey: ?string, renderItem?: ?RenderItemType, ... }; type SeparatorProps = $ReadOnly<{| highlighted: boolean, leadingItem: ?ItemT, |}>; type State = { separatorProps: SeparatorProps, ... }; export default class CellRenderer extends React.Component< Props, State, > { state: State = { separatorProps: { highlighted: false, leadingItem: this.props.item, }, }; static getDerivedStateFromProps( props: Props, prevState: State, ): ?State { return { separatorProps: { ...prevState.separatorProps, leadingItem: props.item, }, }; } // TODO: consider factoring separator stuff out of VirtualizedList into FlatList since it's not // reused by SectionList and we can keep VirtualizedList simpler. // $FlowFixMe[missing-local-annot] _separators = { highlight: () => { const {cellKey, prevCellKey} = this.props; this.props.onUpdateSeparators([cellKey, prevCellKey], { highlighted: true, }); }, unhighlight: () => { const {cellKey, prevCellKey} = this.props; this.props.onUpdateSeparators([cellKey, prevCellKey], { highlighted: false, }); }, updateProps: ( select: 'leading' | 'trailing', newProps: SeparatorProps, ) => { const {cellKey, prevCellKey} = this.props; this.props.onUpdateSeparators( [select === 'leading' ? prevCellKey : cellKey], newProps, ); }, }; updateSeparatorProps(newProps: SeparatorProps) { this.setState(state => ({ separatorProps: {...state.separatorProps, ...newProps}, })); } componentWillUnmount() { this.props.onUnmount(this.props.cellKey); } _onLayout = (nativeEvent: LayoutEvent): void => { this.props.onCellLayout && this.props.onCellLayout( nativeEvent, this.props.cellKey, this.props.index, ); }; _renderElement( renderItem: ?RenderItemType, ListItemComponent: any, item: ItemT, index: number, ): React.Node { if (renderItem && ListItemComponent) { console.warn( 'VirtualizedList: Both ListItemComponent and renderItem props are present. ListItemComponent will take' + ' precedence over renderItem.', ); } if (ListItemComponent) { /* $FlowFixMe[not-a-component] (>=0.108.0 site=react_native_fb) This * comment suppresses an error found when Flow v0.108 was deployed. To * see the error, delete this comment and run Flow. */ /* $FlowFixMe[incompatible-type-arg] (>=0.108.0 site=react_native_fb) * This comment suppresses an error found when Flow v0.108 was deployed. * To see the error, delete this comment and run Flow. */ return React.createElement(ListItemComponent, { item, index, separators: this._separators, }); } if (renderItem) { return renderItem({ item, index, separators: this._separators, }); } invariant( false, 'VirtualizedList: Either ListItemComponent or renderItem props are required but none were found.', ); } render(): React.Node { const { CellRendererComponent, ItemSeparatorComponent, ListItemComponent, cellKey, horizontal, item, index, inversionStyle, onCellFocusCapture, onCellLayout, renderItem, } = this.props; const element = this._renderElement( renderItem, ListItemComponent, item, index, ); // NOTE: that when this is a sticky header, `onLayout` will get automatically extracted and // called explicitly by `ScrollViewStickyHeader`. const itemSeparator: React.Node = React.isValidElement( ItemSeparatorComponent, ) ? // $FlowFixMe[incompatible-type] ItemSeparatorComponent : // $FlowFixMe[incompatible-type] ItemSeparatorComponent && ( ); const cellStyle = inversionStyle ? horizontal ? [styles.rowReverse, inversionStyle] : [styles.columnReverse, inversionStyle] : horizontal ? [styles.row, inversionStyle] : inversionStyle; const result = !CellRendererComponent ? ( {element} {itemSeparator} ) : ( {element} {itemSeparator} ); return ( {result} ); } } const styles = StyleSheet.create({ row: { flexDirection: 'row', }, rowReverse: { flexDirection: 'row-reverse', }, columnReverse: { flexDirection: 'column-reverse', }, });