diff --git a/packages/react-native-web/src/exports/SectionList/index.js b/packages/react-native-web/src/exports/SectionList/index.js index 334bacc3..15152474 100644 --- a/packages/react-native-web/src/exports/SectionList/index.js +++ b/packages/react-native-web/src/exports/SectionList/index.js @@ -7,5 +7,5 @@ * @flow */ -import UnimplementedView from '../../modules/UnimplementedView'; -export default UnimplementedView; +import SectionList from '../../vendor/react-native/SectionList'; +export default SectionList; 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 new file mode 100644 index 00000000..9e7ff75c --- /dev/null +++ b/packages/react-native-web/src/vendor/react-native/SectionList/index.js @@ -0,0 +1,341 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @noflow + * @format + */ +import UnimplementedView from '../../../modules/UnimplementedView'; +import Platform from '../../../exports/Platform'; +import React from 'react'; +import ScrollView from '../../../exports/ScrollView'; +import VirtualizedSectionList, { + type Props as VirtualizedSectionListProps +} from '../VirtualizedSectionList'; +import type { ViewToken } from '../ViewabilityHelper'; + +type Item = any; + +export type SectionBase = { + /** + * The data for rendering items in this section. + */ + data: $ReadOnlyArray, + /** + * Optional key to keep track of section re-ordering. If you don't plan on re-ordering sections, + * the array index will be used by default. + */ + key?: string, + + // Optional props will override list-wide props just for this section. + renderItem?: ?(info: { + item: SectionItemT, + index: number, + section: SectionBase, + separators: { + highlight: () => void, + unhighlight: () => void, + updateProps: (select: 'leading' | 'trailing', newProps: Object) => void, + }, + }) => ?React.Element, + ItemSeparatorComponent?: ?React.ComponentType, + keyExtractor?: (item: SectionItemT) => string, + + // TODO: support more optional/override props + // onViewableItemsChanged?: ... +}; + +type RequiredProps> = { + /** + * The actual data to render, akin to the `data` prop in [``](/react-native/docs/flatlist.html). + * + * General shape: + * + * sections: $ReadOnlyArray<{ + * data: $ReadOnlyArray, + * renderItem?: ({item: SectionItem, ...}) => ?React.Element<*>, + * ItemSeparatorComponent?: ?ReactClass<{highlighted: boolean, ...}>, + * }> + */ + sections: $ReadOnlyArray, +}; + +type OptionalProps> = { + /** + * Default renderer for every item in every section. Can be over-ridden on a per-section basis. + */ + renderItem?: (info: { + item: Item, + index: number, + section: SectionT, + separators: { + 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, + /** + * 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, + /** + * 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, + /** + * 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. Note that this sets keys for each item, but + * each overall section still needs its own key. + */ + 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, + /** + * 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, + + legacyImplementation?: ?boolean, +}; + +export type Props = RequiredProps & + OptionalProps & + VirtualizedSectionListProps; + +const defaultProps = { + ...VirtualizedSectionList.defaultProps, + stickySectionHeadersEnabled: Platform.OS === 'ios', +}; + +type DefaultProps = typeof defaultProps; + +/** + * A performant interface for rendering sectioned lists, supporting the most handy features: + * + * - Fully cross-platform. + * - Configurable viewability callbacks. + * - List header support. + * - List footer support. + * - Item separator support. + * - Section header support. + * - Section separator support. + * - Heterogeneous data and item rendering support. + * - Pull to Refresh. + * - Scroll loading. + * + * If you don't need section support and want a simpler interface, use + * [``](/react-native/docs/flatlist.html). + * + * Simple Examples: + * + * } + * renderSectionHeader={({section}) =>
} + * sections={[ // homogeneous rendering between sections + * {data: [...], title: ...}, + * {data: [...], title: ...}, + * {data: [...], title: ...}, + * ]} + * /> + * + * + * + * 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 and 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. + * + */ +class SectionList> extends React.PureComponent< + Props, + void, +> { + props: Props; + static defaultProps: DefaultProps = defaultProps; + + /** + * Scrolls to the item at the specified `sectionIndex` and `itemIndex` (within the section) + * positioned in the viewable area such that `viewPosition` 0 places it at the top (and may be + * covered by a sticky header), 1 at the bottom, and 0.5 centered in the middle. `viewOffset` is a + * fixed number of pixels to offset the final target position, e.g. to compensate for sticky + * headers. + * + * Note: cannot scroll to locations outside the render window without specifying the + * `getItemLayout` prop. + */ + scrollToLocation(params: { + animated?: ?boolean, + itemIndex: number, + sectionIndex: number, + viewOffset?: number, + viewPosition?: number, + }) { + this._wrapperListRef.scrollToLocation(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() { + const listRef = this._wrapperListRef && this._wrapperListRef.getListRef(); + listRef && listRef.recordInteraction(); + } + + /** + * Displays the scroll indicators momentarily. + * + * @platform ios + */ + flashScrollIndicators() { + const listRef = this._wrapperListRef && this._wrapperListRef.getListRef(); + listRef && listRef.flashScrollIndicators(); + } + + /** + * Provides a handle to the underlying scroll responder. + */ + getScrollResponder(): ?ScrollView { + const listRef = this._wrapperListRef && this._wrapperListRef.getListRef(); + if (listRef) { + return listRef.getScrollResponder(); + } + } + + getScrollableNode() { + const listRef = this._wrapperListRef && this._wrapperListRef.getListRef(); + if (listRef) { + return listRef.getScrollableNode(); + } + } + + setNativeProps(props: Object) { + const listRef = this._wrapperListRef && this._wrapperListRef.getListRef(); + if (listRef) { + listRef.setNativeProps(props); + } + } + + render() { + const List = this.props.legacyImplementation + ? UnimplementedView + : VirtualizedSectionList; + /* $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. */ + return ; + } + + _wrapperListRef: VirtualizedSectionList; + _captureRef = ref => { + /* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This comment + * suppresses an error when upgrading Flow's support for React. To see the + * error delete this comment and run Flow. */ + this._wrapperListRef = ref; + }; +} + +export default SectionList; 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 new file mode 100644 index 00000000..8e213f63 --- /dev/null +++ b/packages/react-native-web/src/vendor/react-native/VirtualizedSectionList/index.js @@ -0,0 +1,522 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @noflow + * @format + */ + +import React from 'react'; +import View from '../../../exports/View'; +import VirtualizedList, { type Props as VirtualizedListProps } from '../VirtualizedList'; +import invariant from 'fbjs/lib/invariant'; +import type { ViewToken } from '../ViewabilityHelper'; + +type Item = any; +type SectionItem = any; + +type SectionBase = { + // Must be provided directly on each section. + data: $ReadOnlyArray, + key?: string, + + // Optional props will override list-wide props just for this section. + renderItem?: ?({ + item: SectionItem, + index: number, + section: SectionBase, + separators: { + highlight: () => void, + unhighlight: () => void, + updateProps: (select: 'leading' | 'trailing', newProps: Object) => void, + }, + }) => ?React.Element, + ItemSeparatorComponent?: ?React.ComponentType<*>, + keyExtractor?: (item: SectionItem, index: ?number) => string, + + // TODO: support more optional/override props + // FooterComponent?: ?ReactClass<*>, + // HeaderComponent?: ?ReactClass<*>, + // onViewableItemsChanged?: ({viewableItems: Array, changed: Array}) => void, +}; + +type RequiredProps = { + sections: $ReadOnlyArray, +}; + +type OptionalProps = { + /** + * Rendered after the last item in the last section. + */ + ListFooterComponent?: ?(React.ComponentType<*> | React.Element), + /** + * Rendered at the very beginning of the list. + */ + ListHeaderComponent?: ?(React.ComponentType<*> | React.Element), + /** + * Default renderer for every item in every section. + */ + renderItem?: (info: { + item: Item, + index: number, + section: SectionT, + separators: { + highlight: () => void, + unhighlight: () => void, + updateProps: (select: 'leading' | 'trailing', newProps: Object) => void, + }, + }) => ?React.Element, + /** + * Rendered at the top of each section. + */ + renderSectionHeader?: ?({section: SectionT}) => ?React.Element, + /** + * Rendered at the bottom of each section. + */ + renderSectionFooter?: ?({section: SectionT}) => ?React.Element, + /** + * Rendered at the bottom of every Section, except the very last one, in place of the normal + * ItemSeparatorComponent. + */ + SectionSeparatorComponent?: ?React.ComponentType<*>, + /** + * Rendered at the bottom of every Item except the very last one in the last section. + */ + ItemSeparatorComponent?: ?React.ComponentType<*>, + /** + * Warning: Virtualization can drastically improve memory consumption for long lists, but trashes + * the state of items when they scroll out of the render window, so make sure all relavent data is + * stored outside of the recursive `renderItem` instance tree. + */ + enableVirtualization?: ?boolean, + keyExtractor: (item: Item, index: number) => string, + onEndReached?: ?({distanceFromEnd: number}) => void, + /** + * If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make + * sure to also set the `refreshing` prop correctly. + */ + onRefresh?: ?Function, + /** + * Called when the viewability of rows changes, as defined by the + * `viewabilityConfig` prop. + */ + onViewableItemsChanged?: ?({ + viewableItems: Array, + changed: Array, + }) => void, + /** + * Set this true while waiting for new data from a refresh. + */ + refreshing?: ?boolean, +}; + +export type Props = RequiredProps & + OptionalProps & + VirtualizedListProps; + +type DefaultProps = typeof VirtualizedList.defaultProps & { + data: $ReadOnlyArray, +}; +type State = {childProps: VirtualizedListProps}; + +/** + * Right now this just flattens everything into one list and uses VirtualizedList under the + * hood. The only operation that might not scale well is concatting the data arrays of all the + * sections when new props are received, which should be plenty fast for up to ~10,000 items. + */ +class VirtualizedSectionList extends React.PureComponent< + Props, + State, +> { + props: Props; + + state: State; + + static defaultProps: DefaultProps = { + ...VirtualizedList.defaultProps, + data: [], + }; + + scrollToLocation(params: { + animated?: ?boolean, + itemIndex: number, + sectionIndex: number, + viewPosition?: number, + }) { + let index = params.itemIndex + 1; + for (let ii = 0; ii < params.sectionIndex; ii++) { + index += this.props.sections[ii].data.length + 2; + } + const toIndexParams = { + ...params, + index, + }; + this._listRef.scrollToIndex(toIndexParams); + } + + getListRef(): VirtualizedList { + return this._listRef; + } + + _keyExtractor = (item: Item, index: number) => { + const info = this._subExtractor(index); + return (info && info.key) || String(index); + }; + + _subExtractor( + index: number, + ): ?{ + section: SectionT, + key: string, // Key of the section or combined key for section + item + index: ?number, // Relative index within the section + header?: ?boolean, // True if this is the section header + leadingItem?: ?Item, + leadingSection?: ?SectionT, + trailingItem?: ?Item, + trailingSection?: ?SectionT, + } { + let itemIndex = index; + const defaultKeyExtractor = this.props.keyExtractor; + for (let ii = 0; ii < this.props.sections.length; ii++) { + const section = this.props.sections[ii]; + const key = section.key || String(ii); + itemIndex -= 1; // The section adds an item for the header + if (itemIndex >= section.data.length + 1) { + itemIndex -= section.data.length + 1; // The section adds an item for the footer. + } else if (itemIndex === -1) { + return { + section, + key: key + ':header', + index: null, + header: true, + trailingSection: this.props.sections[ii + 1], + }; + } else if (itemIndex === section.data.length) { + return { + section, + key: key + ':footer', + index: null, + header: false, + trailingSection: this.props.sections[ii + 1], + }; + } else { + const keyExtractor = section.keyExtractor || defaultKeyExtractor; + return { + section, + key: key + ':' + keyExtractor(section.data[itemIndex], itemIndex), + index: itemIndex, + leadingItem: section.data[itemIndex - 1], + leadingSection: this.props.sections[ii - 1], + trailingItem: section.data[itemIndex + 1], + trailingSection: this.props.sections[ii + 1], + }; + } + } + } + + _convertViewable = (viewable: ViewToken): ?ViewToken => { + invariant(viewable.index != null, 'Received a broken ViewToken'); + const info = this._subExtractor(viewable.index); + if (!info) { + return null; + } + const keyExtractor = info.section.keyExtractor || this.props.keyExtractor; + 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), + section: info.section, + }; + }; + + _onViewableItemsChanged = ({ + viewableItems, + changed, + }: { + viewableItems: Array, + changed: Array, + }) => { + if (this.props.onViewableItemsChanged) { + this.props.onViewableItemsChanged({ + viewableItems: viewableItems + .map(this._convertViewable, this) + .filter(Boolean), + changed: changed.map(this._convertViewable, this).filter(Boolean), + }); + } + }; + + _renderItem = ({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; + } + } else { + const renderItem = info.section.renderItem || this.props.renderItem; + const SeparatorComponent = this._getSeparatorComponent(index, info); + invariant(renderItem, 'no renderItem!'); + return ( + { + this._cellRefs[info.key] = ref; + }} + renderItem={renderItem} + section={info.section} + trailingItem={info.trailingItem} + trailingSection={info.trailingSection} + /> + ); + } + }; + + _onUpdateSeparator = (key: string, newProps: Object) => { + const ref = this._cellRefs[key]; + ref && ref.updateSeparatorProps(newProps); + }; + + _getSeparatorComponent( + index: number, + info?: ?Object, + ): ?React.ComponentType<*> { + info = info || this._subExtractor(index); + if (!info) { + return null; + } + const ItemSeparatorComponent = + info.section.ItemSeparatorComponent || this.props.ItemSeparatorComponent; + const {SectionSeparatorComponent} = this.props; + const isLastItemInList = index === this.state.childProps.getItemCount() - 1; + const isLastItemInSection = info.index === info.section.data.length - 1; + if (SectionSeparatorComponent && isLastItemInSection) { + return SectionSeparatorComponent; + } + if (ItemSeparatorComponent && !isLastItemInSection && !isLastItemInList) { + return ItemSeparatorComponent; + } + return null; + } + + _computeState(props: Props): State { + const offset = props.ListHeaderComponent ? 1 : 0; + const stickyHeaderIndices = []; + const itemCount = props.sections.reduce((v, section) => { + stickyHeaderIndices.push(v + offset); + return v + section.data.length + 2; // Add two for the section header and footer. + }, 0); + + return { + childProps: { + ...props, + renderItem: this._renderItem, + ItemSeparatorComponent: undefined, // Rendered with renderItem + data: props.sections, + getItemCount: () => itemCount, + getItem, + keyExtractor: this._keyExtractor, + onViewableItemsChanged: props.onViewableItemsChanged + ? this._onViewableItemsChanged + : undefined, + stickyHeaderIndices: props.stickySectionHeadersEnabled + ? stickyHeaderIndices + : undefined, + }, + }; + } + + constructor(props: Props, context: Object) { + super(props, context); + this.state = this._computeState(props); + } + + UNSAFE_componentWillReceiveProps(nextProps: Props) { + this.setState(this._computeState(nextProps)); + } + + render() { + return ( + + ); + } + + _cellRefs = {}; + _listRef: VirtualizedList; + _captureRef = ref => { + /* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This comment + * suppresses an error when upgrading Flow's support for React. To see the + * error delete this comment and run Flow. */ + this._listRef = ref; + }; +} + +type ItemWithSeparatorProps = { + LeadingSeparatorComponent: ?React.ComponentType<*>, + SeparatorComponent: ?React.ComponentType<*>, + cellKey: string, + index: number, + item: Item, + onUpdateSeparator: (cellKey: string, newProps: Object) => void, + prevCellKey?: ?string, + renderItem: Function, + section: Object, + leadingItem: ?Item, + leadingSection: ?Object, + trailingItem: ?Item, + trailingSection: ?Object, +}; + +class ItemWithSeparator extends React.Component< + ItemWithSeparatorProps, + $FlowFixMeState, +> { + 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, + }, + }; + + _separators = { + highlight: () => { + ['leading', 'trailing'].forEach(s => + this._separators.updateProps(s, {highlighted: true}), + ); + }, + unhighlight: () => { + ['leading', 'trailing'].forEach(s => + this._separators.updateProps(s, {highlighted: false}), + ); + }, + updateProps: (select: 'leading' | 'trailing', newProps: Object) => { + const {LeadingSeparatorComponent, cellKey, prevCellKey} = this.props; + if (select === 'leading' && LeadingSeparatorComponent) { + this.setState(state => ({ + leadingSeparatorProps: {...state.leadingSeparatorProps, ...newProps}, + })); + } else { + this.props.onUpdateSeparator( + (select === 'leading' && prevCellKey) || cellKey, + newProps, + ); + } + }, + }; + + UNSAFE_componentWillReceiveProps(props: ItemWithSeparatorProps) { + this.setState(state => ({ + separatorProps: { + ...this.state.separatorProps, + leadingItem: props.item, + leadingSection: props.leadingSection, + section: props.section, + trailingItem: props.trailingItem, + trailingSection: props.trailingSection, + }, + leadingSeparatorProps: { + ...this.state.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, + } = this.props; + const element = this.props.renderItem({ + item, + index, + section, + separators: this._separators, + }); + const leadingSeparator = LeadingSeparatorComponent && ( + + ); + const separator = SeparatorComponent && ( + + ); + return leadingSeparator || separator ? ( + + {leadingSeparator} + {element} + {separator} + + ) : ( + element + ); + } +} + +function getItem(sections: ?$ReadOnlyArray, index: number): ?Item { + if (!sections) { + return null; + } + let itemIdx = index - 1; + for (let ii = 0; ii < sections.length; ii++) { + if (itemIdx === -1 || itemIdx === sections[ii].data.length) { + // We intend for there to be overflow by one on both ends of the list. + // This will be for headers and footers. When returning a header or footer + // item the section itself is the item. + return sections[ii]; + } else if (itemIdx < sections[ii].data.length) { + // If we are in the bounds of the list's data then return the item. + return sections[ii].data[itemIdx]; + } else { + itemIdx -= sections[ii].data.length + 2; // Add two for the header and footer + } + } + return null; +} + +export default VirtualizedSectionList;