UNPKG

@react-native/virtualized-lists

Version:
252 lines (231 loc) • 6.37 kB
/** * 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 {CellRendererProps, ListRenderItem} from './VirtualizedListProps'; import type { FocusEvent, LayoutChangeEvent, StyleProp, ViewStyle, } from 'react-native'; import {VirtualizedListCellContextProvider} from './VirtualizedListContext.js'; import invariant from 'invariant'; import * as React from 'react'; import {StyleSheet, View} from 'react-native'; export type Props<ItemT> = { CellRendererComponent?: ?React.ComponentType<CellRendererProps<ItemT>>, ItemSeparatorComponent: ?React.ComponentType< any | {highlighted: boolean, leadingItem: ?ItemT}, >, ListItemComponent?: ?(React.ComponentType<any> | React.MixedElement), cellKey: string, horizontal: ?boolean, index: number, inversionStyle: StyleProp<ViewStyle>, item: ItemT, onCellLayout?: ( event: LayoutChangeEvent, cellKey: string, index: number, ) => void, onCellFocusCapture?: (cellKey: string) => void, onUnmount: (cellKey: string) => void, onUpdateSeparators: ( cellKeys: Array<?string>, props: Partial<SeparatorProps<ItemT>>, ) => void, prevCellKey: ?string, renderItem?: ?ListRenderItem<ItemT>, ... }; type SeparatorProps<ItemT> = $ReadOnly<{ highlighted: boolean, leadingItem: ?ItemT, }>; type State<ItemT> = { separatorProps: SeparatorProps<ItemT>, ... }; export default class CellRenderer<ItemT> extends React.PureComponent< Props<ItemT>, State<ItemT>, > { state: State<ItemT> = { separatorProps: { highlighted: false, leadingItem: this.props.item, }, }; static getDerivedStateFromProps<StaticItemT>( props: Props<StaticItemT>, prevState: State<StaticItemT>, ): ?State<StaticItemT> { if (props.item !== prevState.separatorProps.leadingItem) { return { separatorProps: { ...prevState.separatorProps, leadingItem: props.item, }, }; } return null; } // TODO: consider factoring separator stuff out of VirtualizedList into FlatList since it's not // reused by SectionList and we can keep VirtualizedList simpler. // $FlowFixMe[missing-local-annot] _separators = { highlight: () => { const {cellKey, prevCellKey} = this.props; this.props.onUpdateSeparators([cellKey, prevCellKey], { highlighted: true, }); }, unhighlight: () => { const {cellKey, prevCellKey} = this.props; this.props.onUpdateSeparators([cellKey, prevCellKey], { highlighted: false, }); }, updateProps: ( select: 'leading' | 'trailing', newProps: SeparatorProps<ItemT>, ) => { const {cellKey, prevCellKey} = this.props; this.props.onUpdateSeparators( [select === 'leading' ? prevCellKey : cellKey], newProps, ); }, }; updateSeparatorProps(newProps: SeparatorProps<ItemT>) { this.setState(state => ({ separatorProps: {...state.separatorProps, ...newProps}, })); } componentWillUnmount() { this.props.onUnmount(this.props.cellKey); } _onLayout = (nativeEvent: LayoutChangeEvent): void => { this.props.onCellLayout?.( nativeEvent, this.props.cellKey, this.props.index, ); }; _onCellFocusCapture = (e: FocusEvent): void => { this.props.onCellFocusCapture?.(this.props.cellKey); }; _renderElement( renderItem: ?ListRenderItem<ItemT>, ListItemComponent: any, item: ItemT, index: number, ): React.Node { if (renderItem && ListItemComponent) { console.warn( 'VirtualizedList: Both ListItemComponent and renderItem props are present. ListItemComponent will take' + ' precedence over renderItem.', ); } if (ListItemComponent) { return ( <ListItemComponent item={item} index={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, onCellLayout, renderItem, } = this.props; const element = this._renderElement( renderItem, ListItemComponent, item, index, ); // NOTE: that when this is a sticky header, `onLayout` will get automatically extracted and // called explicitly by `ScrollViewStickyHeader`. const itemSeparator: React.Node = React.isValidElement( ItemSeparatorComponent, ) ? // $FlowFixMe[incompatible-type] ItemSeparatorComponent : // $FlowFixMe[incompatible-type] ItemSeparatorComponent && ( <ItemSeparatorComponent {...this.state.separatorProps} /> ); const cellStyle = inversionStyle ? horizontal ? [styles.rowReverse, inversionStyle] : [styles.columnReverse, inversionStyle] : horizontal ? [styles.row, inversionStyle] : inversionStyle; const result = !CellRendererComponent ? ( <View style={cellStyle} onFocusCapture={this._onCellFocusCapture} {...(onCellLayout && {onLayout: this._onLayout})}> {element} {itemSeparator} </View> ) : ( <CellRendererComponent cellKey={cellKey} index={index} item={item} style={cellStyle} onFocusCapture={this._onCellFocusCapture} {...(onCellLayout && {onLayout: this._onLayout})}> {element} {itemSeparator} </CellRendererComponent> ); return ( <VirtualizedListCellContextProvider cellKey={this.props.cellKey}> {result} </VirtualizedListCellContextProvider> ); } } const styles = StyleSheet.create({ row: { flexDirection: 'row', }, rowReverse: { flexDirection: 'row-reverse', }, columnReverse: { flexDirection: 'column-reverse', }, });