import React from 'react'; import Style from '@localTypes/style'; import { LayoutEvent } from '@localTypes/event'; import { callUIFunction } from '../modules/ui-manager-module'; import { warn } from '../utils'; import { Device } from '../native'; type DataItem = any; interface ListViewProps { /** * Render specific number of rows of data. * Set equal to dataShource.length in most case. */ numberOfRows: number; /** * Data source */ dataSource: DataItem[]; /** * Specfic how many data will render at first screen. */ initialListSize?: number; /** * Scroll to offset after `ListView` with data rendered. */ initialContentOffset?: number; /** * This controls how often the scroll event will be fired while scrolling * (as a time interval in ms). A lower number yields better accuracy for code * that is tracking the scroll position, but can lead to scroll performance * problems due to the volume of information being send over the bridge. * You will not notice a difference between values set between 1-16 as the JS run loop * is synced to the screen refresh rate. If you do not need precise scroll position tracking, * set this value higher to limit the information being sent across the bridge. * * The default value is zero, which results in the scroll event being sent only once * each time the view is scrolled. */ scrollEventThrottle: number; /** * When `true`, shows a horizon scroll indicator. * The default value is `true`. */ showScrollIndicator?: boolean; /** * Passing the data and returns the row component. * * @param {Object} data - Data for row rendering * @param {null} unknown - seems null. * @param {number} index - Index Of data. * @returns {React.Component} */ renderRow( data: DataItem, unknown?: any, // FIXME: What's the argument meaning? index?: number, ): React.ReactElement; /** * Each row have different type, it will be using at render recycle. * * @param {number} index - Index Of data. * @returns {string} */ getRowType?(index: number): string; /** * Returns the style for specific index of row. * * @param {number} index - Index Of data. * @returns {Object} */ getRowStyle?(index: number): Style; /** * Specfic the key of row, for better data diff * More info: https://reactjs.org/docs/lists-and-keys.html * * @param {number} index - Index Of data. * @returns {string} */ getRowKey?(index: number): string; /** * Is the row should sticky after scrolling up. * @param {number} index - Index Of data. * @returns {boolean} */ rowShouldSticky?(index: number): boolean; style?: Style; /** * Called when the `ListView` is scrolling to bottom. */ onEndReached?(): void; /** * Called when the row first layouting or layout changed. * * @param {Object} evt - Layout event data * @param {number} evt.nativeEvent.x - The position X of component * @param {number} evt.nativeEvent.y - The position Y of component * @param {number} evt.nativeEvent.width - The width of component * @param {number} evt.nativeEvent.hegiht - The height of component * @param {number} index - Index of data. */ onRowLayout?(evt: LayoutEvent, index: number): void; /** * Called when the momentum scroll starts (scroll which occurs as the ListView starts gliding). */ onMomentumScrollBegin?(): void; /** * Called when the momentum scroll ends (scroll which occurs as the ListView glides to a stop). */ onMomentumScrollEnd?(): void; /** * Fires at most once per frame during scrolling. * The frequency of the events can be controlled using the `scrollEventThrottle` prop. * * @param {Object} evt - Scroll event data. * @param {number} evt.contentOffset.x - Offset X of scrolling. * @param {number} evt.contentOffset.y - Offset Y of scrolling. */ onScroll?(evt: { contentOffset: { x: number, y: number }}): void; // FIXME: TS compile error. /** * Called when the user begins to drag the scroll view. */ onScrollBeginDrag?(): void; /** * Called when the user stops dragging the scroll view and it either stops or begins to glide. */ onScrollEndDrag?(): void; } interface ListItemViewProps { type?: string; key?: string; itemViewType?: string; sticky?: boolean; style?: Style; onLayout?: (evt: any) => void; } interface ListViewState { initialListReady: boolean; } function ListViewItem(props: ListItemViewProps) { return (
  • ); } /** * Recyclable list for better performance, and lower memory usage. * @noInheritDoc */ class ListView extends React.Component { private instance: HTMLUListElement | null = null; /** * @ignore */ constructor(props: ListViewProps) { super(props); this.handleInitialListReady = this.handleInitialListReady.bind(this); this.state = { initialListReady: false, }; } /** * @ignore */ public componentDidMount() { const { getRowKey } = this.props; if (!getRowKey) { warn('ListView needs getRowKey to specific the key of item'); } } /** * Scrolls to a given index of itme, either immediately, with a smooth animation. * * @param {number} xIndex - Scroll to horizon index X. * @param {number} yIndex - Scroll To veritical index Y. * @param {boolean} animated - With smooth animation.By default is true. */ public scrollToIndex(xIndex: number, yIndex: number, animated: boolean) { if (typeof xIndex !== 'number' || typeof yIndex !== 'number' || typeof animated !== 'boolean') { return; } callUIFunction(this.instance, 'scrollToIndex', [xIndex, yIndex, animated]); } /** * Scrolls to a given x, y offset, either immediately, with a smooth animation. * * @param {number} xOffset - Scroll to horizon offset X. * @param {number} yOffset - Scroll To veritical offset Y. * @param {boolean} animated - With smooth animation.By default is true. */ public scrollToContentOffset(xOffset: number, yOffset: number, animated: boolean) { if (typeof xOffset !== 'number' || typeof yOffset !== 'number' || typeof animated !== 'boolean') { return; } callUIFunction(this.instance, 'scrollToContentOffset', [xOffset, yOffset, animated]); } private handleInitialListReady() { this.setState({ initialListReady: true }); } /** * @ignore */ public render() { let { numberOfRows } = this.props; const { style, renderRow, getRowType, getRowStyle, getRowKey, dataSource, initialListSize, rowShouldSticky, onRowLayout, ...nativeProps } = this.props; if (typeof renderRow !== 'function') { throw new Error('renderRow props is necessary for ListView'); } const { initialListReady, } = this.state; const itemList = []; if (!numberOfRows && dataSource) { numberOfRows = dataSource.length; } if (!initialListReady) { numberOfRows = Math.min(numberOfRows, (initialListSize || 10)); } for (let index = 0; index < numberOfRows; index += 1) { const itemProps: ListItemViewProps = {}; let rowChildren; if (dataSource) { rowChildren = renderRow(dataSource[index], null, index); } else { rowChildren = renderRow(index); } if (typeof getRowKey === 'function') { itemProps.key = getRowKey(index); } if (typeof getRowStyle === 'function') { itemProps.style = getRowStyle(index); } if (typeof onRowLayout === 'function') { itemProps.onLayout = (e: any) => { onRowLayout(e, index); }; } if (typeof getRowType === 'function') { let type = getRowType(index); if (type) { if (typeof type !== 'string') { type = (type as any).toString(); } itemProps.type = type; if (Device.platform.OS === 'android') { itemProps.itemViewType = type; } } } if (typeof rowShouldSticky === 'function') { itemProps.sticky = rowShouldSticky(index); } if (rowChildren) { itemList.push(( {rowChildren} )); } } nativeProps.numberOfRows = itemList.length; (nativeProps as ListViewProps).initialListSize = initialListSize; (nativeProps as ListViewProps).style = { overflow: 'scroll', ...style, }; return ( ); } } /** * @ignore */ ListView.defaultProps = { numberOfRows: 0, }; export default ListView;