diff --git a/packages/react-native/Libraries/Lists/__tests__/__snapshots__/FlatList-test.js.snap b/packages/react-native/Libraries/Lists/__tests__/__snapshots__/FlatList-test.js.snap index 2fec3a235fb118..86901053a00d9f 100644 --- a/packages/react-native/Libraries/Lists/__tests__/__snapshots__/FlatList-test.js.snap +++ b/packages/react-native/Libraries/Lists/__tests__/__snapshots__/FlatList-test.js.snap @@ -111,6 +111,7 @@ exports[`FlatList renders all the bells and whistles 1`] = `
diff --git a/packages/react-native/Libraries/Lists/__tests__/__snapshots__/SectionList-test.js.snap b/packages/react-native/Libraries/Lists/__tests__/__snapshots__/SectionList-test.js.snap index ea1021883c1d92..a700cdb7be2a73 100644 --- a/packages/react-native/Libraries/Lists/__tests__/__snapshots__/SectionList-test.js.snap +++ b/packages/react-native/Libraries/Lists/__tests__/__snapshots__/SectionList-test.js.snap @@ -243,6 +243,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
{ +function genItemData(i: number): Item { + const itemHash = Math.abs(hashCode('Item ' + i)); + return { + title: 'Item ' + i, + text: LOREM_IPSUM.substr(0, (itemHash % 301) + 20), + key: String(i), + pressed: false, + }; +} + +function genNewerItems(count: number, start: number = 0): Array { + const dataBlob = []; + for (let i = start; i < count + start; i++) { + dataBlob.push(genItemData(i)); + } + return dataBlob; +} + +function genOlderItems(count: number, start: number = 0): Array { const dataBlob = []; - for (let ii = start; ii < count + start; ii++) { - const itemHash = Math.abs(hashCode('Item ' + ii)); - dataBlob.push({ - title: 'Item ' + ii, - text: LOREM_IPSUM.substr(0, (itemHash % 301) + 20), - key: String(ii), - pressed: false, - }); + for (let i = count; i > 0; i--) { + dataBlob.push(genItemData(start - i)); } return dataBlob; } @@ -147,6 +160,12 @@ class SeparatorComponent extends React.PureComponent<{...}> { } } +const LoadingComponent: React.ComponentType<{}> = React.memo(() => ( + + + +)); + class ItemSeparatorComponent extends React.PureComponent<$FlowFixMeProps> { render(): React.Node { const style = this.props.highlighted @@ -352,6 +371,13 @@ const styles = StyleSheet.create({ text: { flex: 1, }, + loadingContainer: { + alignItems: 'center', + justifyContent: 'center', + height: 100, + borderTopWidth: 1, + borderTopColor: 'rgb(200, 199, 204)', + }, }); module.exports = { @@ -362,8 +388,10 @@ module.exports = { ItemSeparatorComponent, PlainInput, SeparatorComponent, + LoadingComponent, Spindicator, - genItemData, + genNewerItems, + genOlderItems, getItemLayout, pressItem, renderSmallSwitchOption, diff --git a/packages/rn-tester/js/examples/FlatList/FlatList-basic.js b/packages/rn-tester/js/examples/FlatList/FlatList-basic.js index 97f991265de3ba..7ffaedef0cd3de 100644 --- a/packages/rn-tester/js/examples/FlatList/FlatList-basic.js +++ b/packages/rn-tester/js/examples/FlatList/FlatList-basic.js @@ -35,8 +35,10 @@ import { ItemSeparatorComponent, PlainInput, SeparatorComponent, + LoadingComponent, Spindicator, - genItemData, + genNewerItems, + genOlderItems, getItemLayout, pressItem, renderSmallSwitchOption, @@ -44,6 +46,11 @@ import { import type {Item} from '../../components/ListExampleShared'; +const PAGE_SIZE = 100; +const NUM_PAGES = 10; +const INITIAL_PAGE_OFFSET = Math.floor(NUM_PAGES / 2); +const LOAD_TIME = 2000; + const VIEWABILITY_CONFIG = { minimumViewTime: 3000, viewAreaCoveragePercentThreshold: 100, @@ -53,6 +60,8 @@ const VIEWABILITY_CONFIG = { type Props = $ReadOnly<{||}>; type State = {| data: Array, + first: number, + last: number, debug: boolean, horizontal: boolean, inverted: boolean, @@ -66,13 +75,18 @@ type State = {| onPressDisabled: boolean, textSelectable: boolean, isRTL: boolean, + maintainVisibleContentPosition: boolean, + previousLoading: boolean, + nextLoading: boolean, |}; const IS_RTL = I18nManager.isRTL; class FlatListExample extends React.PureComponent { state: State = { - data: genItemData(100), + data: genNewerItems(PAGE_SIZE, PAGE_SIZE * INITIAL_PAGE_OFFSET), + first: PAGE_SIZE * INITIAL_PAGE_OFFSET, + last: PAGE_SIZE + PAGE_SIZE * INITIAL_PAGE_OFFSET, debug: false, horizontal: false, inverted: false, @@ -86,6 +100,9 @@ class FlatListExample extends React.PureComponent { onPressDisabled: false, textSelectable: true, isRTL: IS_RTL, + maintainVisibleContentPosition: true, + previousLoading: false, + nextLoading: false, }; /* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's @@ -209,6 +226,11 @@ class FlatListExample extends React.PureComponent { this.state.isRTL, this._setIsRTL, )} + {renderSmallSwitchOption( + 'Maintain content position', + this.state.maintainVisibleContentPosition, + this._setBooleanValue('maintainVisibleContentPosition'), + )} {Platform.OS === 'android' && ( { } - ListFooterComponent={FooterComponent} + ListHeaderComponent={ + this.state.previousLoading ? LoadingComponent : HeaderComponent + } + ListFooterComponent={ + this.state.nextLoading ? LoadingComponent : FooterComponent + } ListEmptyComponent={ListEmptyComponent} // $FlowFixMe[missing-empty-array-annot] data={this.state.empty ? [] : filteredData} @@ -250,6 +276,8 @@ class FlatListExample extends React.PureComponent { keyboardShouldPersistTaps="always" keyboardDismissMode="on-drag" numColumns={1} + onStartReached={this._onStartReached} + initialScrollIndex={Math.floor(PAGE_SIZE / 2)} onEndReached={this._onEndReached} onRefresh={this._onRefresh} onScroll={ @@ -260,6 +288,11 @@ class FlatListExample extends React.PureComponent { refreshing={false} contentContainerStyle={styles.list} viewabilityConfig={VIEWABILITY_CONFIG} + maintainVisibleContentPosition={ + this.state.maintainVisibleContentPosition + ? {minIndexForVisible: 0} + : undefined + } {...flatListItemRendererProps} /> @@ -280,13 +313,33 @@ class FlatListExample extends React.PureComponent { _getItemLayout = (data: any, index: number) => { return getItemLayout(data, index, this.state.horizontal); }; + _onStartReached = () => { + if (this.state.first <= 0 || this.state.previousLoading) { + return; + } + + this.setState({previousLoading: true}); + setTimeout(() => { + this.setState(state => ({ + previousLoading: false, + data: genOlderItems(PAGE_SIZE, state.first).concat(state.data), + first: state.first - PAGE_SIZE, + })); + }, LOAD_TIME); + }; _onEndReached = () => { - if (this.state.data.length >= 1000) { + if (this.state.last >= PAGE_SIZE * NUM_PAGES || this.state.nextLoading) { return; } - this.setState(state => ({ - data: state.data.concat(genItemData(100, state.data.length)), - })); + + this.setState({nextLoading: true}); + setTimeout(() => { + this.setState(state => ({ + nextLoading: false, + data: state.data.concat(genNewerItems(PAGE_SIZE, state.last)), + last: state.last + PAGE_SIZE, + })); + }, LOAD_TIME); }; // $FlowFixMe[missing-local-annot] _onPressCallback = () => { @@ -343,7 +396,7 @@ class FlatListExample extends React.PureComponent { _pressItem = (key: string) => { this._listRef?.recordInteraction(); - const index = Number(key); + const index = this.state.data.findIndex(item => item.key === key); const itemState = pressItem(this.state.data[index]); this.setState(state => ({ ...state, diff --git a/packages/rn-tester/js/examples/FlatList/FlatList-multiColumn.js b/packages/rn-tester/js/examples/FlatList/FlatList-multiColumn.js index c92c688840fca2..3d8c8256dccb50 100644 --- a/packages/rn-tester/js/examples/FlatList/FlatList-multiColumn.js +++ b/packages/rn-tester/js/examples/FlatList/FlatList-multiColumn.js @@ -23,7 +23,7 @@ const { ItemComponent, PlainInput, SeparatorComponent, - genItemData, + genNewerItems, getItemLayout, pressItem, renderSmallSwitchOption, @@ -46,7 +46,7 @@ class MultiColumnExample extends React.PureComponent< numColumns: number, virtualized: boolean, |} = { - data: genItemData(1000), + data: genNewerItems(1000), filterText: '', fixedHeight: true, logViewable: false, diff --git a/packages/rn-tester/js/examples/SectionList/SectionList-scrollable.js b/packages/rn-tester/js/examples/SectionList/SectionList-scrollable.js index 815eb03bba887b..248f4e59e3804a 100644 --- a/packages/rn-tester/js/examples/SectionList/SectionList-scrollable.js +++ b/packages/rn-tester/js/examples/SectionList/SectionList-scrollable.js @@ -22,7 +22,7 @@ const { PlainInput, SeparatorComponent, Spindicator, - genItemData, + genNewerItems, pressItem, renderSmallSwitchOption, renderStackedItem, @@ -170,7 +170,7 @@ export function SectionList_scrollable(Props: { const [logViewable, setLogViewable] = React.useState(false); const [debug, setDebug] = React.useState(false); const [inverted, setInverted] = React.useState(false); - const [data, setData] = React.useState(genItemData(1000)); + const [data, setData] = React.useState(genNewerItems(1000)); const filterRegex = new RegExp(String(filterText), 'i'); const filter = (item: Item) => diff --git a/packages/virtualized-lists/Lists/VirtualizedList.js b/packages/virtualized-lists/Lists/VirtualizedList.js index de44f303b07ce9..8a23e81cbef1a6 100644 --- a/packages/virtualized-lists/Lists/VirtualizedList.js +++ b/packages/virtualized-lists/Lists/VirtualizedList.js @@ -73,6 +73,10 @@ type ViewabilityHelperCallbackTuple = { type State = { renderMask: CellRenderMask, cellsAroundViewport: {first: number, last: number}, + // Used to track items added at the start of the list for maintainVisibleContentPosition. + firstVisibleItemKey: ?string, + // When > 0 the scroll position available in JS is considered stale and should not be used. + pendingScrollUpdateCount: number, }; /** @@ -448,9 +452,24 @@ class VirtualizedList extends StateSafePureComponent { const initialRenderRegion = VirtualizedList._initialRenderRegion(props); + const minIndexForVisible = + this.props.maintainVisibleContentPosition?.minIndexForVisible ?? 0; + this.state = { cellsAroundViewport: initialRenderRegion, renderMask: VirtualizedList._createRenderMask(props, initialRenderRegion), + firstVisibleItemKey: + this.props.getItemCount(this.props.data) > minIndexForVisible + ? VirtualizedList._getItemKey(this.props, minIndexForVisible) + : null, + // When we have a non-zero initialScrollIndex, we will receive a + // scroll event later so this will prevent the window from updating + // until we get a valid offset. + pendingScrollUpdateCount: + this.props.initialScrollIndex != null && + this.props.initialScrollIndex > 0 + ? 1 + : 0, }; } @@ -502,6 +521,40 @@ class VirtualizedList extends StateSafePureComponent { } } + static _findItemIndexWithKey( + props: Props, + key: string, + hint: ?number, + ): ?number { + const itemCount = props.getItemCount(props.data); + if (hint != null && hint >= 0 && hint < itemCount) { + const curKey = VirtualizedList._getItemKey(props, hint); + if (curKey === key) { + return hint; + } + } + for (let ii = 0; ii < itemCount; ii++) { + const curKey = VirtualizedList._getItemKey(props, ii); + if (curKey === key) { + return ii; + } + } + return null; + } + + static _getItemKey( + props: { + data: Props['data'], + getItem: Props['getItem'], + keyExtractor: Props['keyExtractor'], + ... + }, + index: number, + ): string { + const item = props.getItem(props.data, index); + return VirtualizedList._keyExtractor(item, index, props); + } + static _createRenderMask( props: Props, cellsAroundViewport: {first: number, last: number}, @@ -585,6 +638,7 @@ class VirtualizedList extends StateSafePureComponent { _adjustCellsAroundViewport( props: Props, cellsAroundViewport: {first: number, last: number}, + pendingScrollUpdateCount: number, ): {first: number, last: number} { const {data, getItemCount} = props; const onEndReachedThreshold = onEndReachedThresholdOrDefault( @@ -616,21 +670,9 @@ class VirtualizedList extends StateSafePureComponent { ), }; } else { - // If we have a non-zero initialScrollIndex and run this before we've scrolled, - // we'll wipe out the initialNumToRender rendered elements starting at initialScrollIndex. - // So let's wait until we've scrolled the view to the right place. And until then, - // we will trust the initialScrollIndex suggestion. - - // Thus, we want to recalculate the windowed render limits if any of the following hold: - // - initialScrollIndex is undefined or is 0 - // - initialScrollIndex > 0 AND scrolling is complete - // - initialScrollIndex > 0 AND the end of the list is visible (this handles the case - // where the list is shorter than the visible area) - if ( - props.initialScrollIndex && - !this._scrollMetrics.offset && - Math.abs(distanceFromEnd) >= Number.EPSILON - ) { + // If we have a pending scroll update, we should not adjust the render window as it + // might override the correct window. + if (pendingScrollUpdateCount > 0) { return cellsAroundViewport.last >= getItemCount(data) ? VirtualizedList._constrainToItemCount(cellsAroundViewport, props) : cellsAroundViewport; @@ -712,14 +754,59 @@ class VirtualizedList extends StateSafePureComponent { return prevState; } + let maintainVisibleContentPositionAdjustment: ?number = null; + const prevFirstVisibleItemKey = prevState.firstVisibleItemKey; + const minIndexForVisible = + newProps.maintainVisibleContentPosition?.minIndexForVisible ?? 0; + const newFirstVisibleItemKey = + newProps.getItemCount(newProps.data) > minIndexForVisible + ? VirtualizedList._getItemKey(newProps, minIndexForVisible) + : null; + if ( + newProps.maintainVisibleContentPosition != null && + prevFirstVisibleItemKey != null && + newFirstVisibleItemKey != null + ) { + if (newFirstVisibleItemKey !== prevFirstVisibleItemKey) { + // Fast path if items were added at the start of the list. + const hint = + itemCount - prevState.renderMask.numCells() + minIndexForVisible; + const firstVisibleItemIndex = VirtualizedList._findItemIndexWithKey( + newProps, + prevFirstVisibleItemKey, + hint, + ); + maintainVisibleContentPositionAdjustment = + firstVisibleItemIndex != null + ? firstVisibleItemIndex - minIndexForVisible + : null; + } else { + maintainVisibleContentPositionAdjustment = null; + } + } + const constrainedCells = VirtualizedList._constrainToItemCount( - prevState.cellsAroundViewport, + maintainVisibleContentPositionAdjustment != null + ? { + first: + prevState.cellsAroundViewport.first + + maintainVisibleContentPositionAdjustment, + last: + prevState.cellsAroundViewport.last + + maintainVisibleContentPositionAdjustment, + } + : prevState.cellsAroundViewport, newProps, ); return { cellsAroundViewport: constrainedCells, renderMask: VirtualizedList._createRenderMask(newProps, constrainedCells), + firstVisibleItemKey: newFirstVisibleItemKey, + pendingScrollUpdateCount: + maintainVisibleContentPositionAdjustment != null + ? prevState.pendingScrollUpdateCount + 1 + : prevState.pendingScrollUpdateCount, }; } @@ -751,7 +838,7 @@ class VirtualizedList extends StateSafePureComponent { for (let ii = first; ii <= last; ii++) { const item = getItem(data, ii); - const key = this._keyExtractor(item, ii, this.props); + const key = VirtualizedList._keyExtractor(item, ii, this.props); this._indicesToKeys.set(ii, key); if (stickyIndicesFromProps.has(ii + stickyOffset)) { @@ -824,15 +911,14 @@ class VirtualizedList extends StateSafePureComponent { _getSpacerKey = (isVertical: boolean): string => isVertical ? 'height' : 'width'; - _keyExtractor( + static _keyExtractor( item: Item, index: number, props: { keyExtractor?: ?(item: Item, index: number) => string, ... }, - // $FlowFixMe[missing-local-annot] - ) { + ): string { if (props.keyExtractor != null) { return props.keyExtractor(item, index); } @@ -878,6 +964,10 @@ class VirtualizedList extends StateSafePureComponent { cellKey={this._getCellKey() + '-header'} key="$header"> { style: inversionStyle ? [inversionStyle, this.props.style] : this.props.style, + maintainVisibleContentPosition: + this.props.maintainVisibleContentPosition != null + ? { + ...this.props.maintainVisibleContentPosition, + // Adjust index to account for ListHeaderComponent. + minIndexForVisible: + this.props.maintainVisibleContentPosition.minIndexForVisible + + (this.props.ListHeaderComponent ? 1 : 0), + } + : undefined, }; this._hasMore = this.state.cellsAroundViewport.last < itemCount - 1; @@ -1456,8 +1556,13 @@ class VirtualizedList extends StateSafePureComponent { onStartReachedThreshold, onEndReached, onEndReachedThreshold, - initialScrollIndex, } = this.props; + // If we have any pending scroll updates it means that the scroll metrics + // are out of date and we should not call any of the edge reached callbacks. + if (this.state.pendingScrollUpdateCount > 0) { + return; + } + const {contentLength, visibleLength, offset} = this._scrollMetrics; let distanceFromStart = offset; let distanceFromEnd = contentLength - visibleLength - offset; @@ -1509,14 +1614,8 @@ class VirtualizedList extends StateSafePureComponent { isWithinStartThreshold && this._scrollMetrics.contentLength !== this._sentStartForContentLength ) { - // On initial mount when using initialScrollIndex the offset will be 0 initially - // and will trigger an unexpected onStartReached. To avoid this we can use - // timestamp to differentiate between the initial scroll metrics and when we actually - // received the first scroll event. - if (!initialScrollIndex || this._scrollMetrics.timestamp !== 0) { - this._sentStartForContentLength = this._scrollMetrics.contentLength; - onStartReached({distanceFromStart}); - } + this._sentStartForContentLength = this._scrollMetrics.contentLength; + onStartReached({distanceFromStart}); } // If the user scrolls away from the start or end and back again, @@ -1643,6 +1742,11 @@ class VirtualizedList extends StateSafePureComponent { visibleLength, zoomScale, }; + if (this.state.pendingScrollUpdateCount > 0) { + this.setState(state => ({ + pendingScrollUpdateCount: state.pendingScrollUpdateCount - 1, + })); + } this._updateViewableItems(this.props, this.state.cellsAroundViewport); if (!this.props) { return; @@ -1758,6 +1862,7 @@ class VirtualizedList extends StateSafePureComponent { const cellsAroundViewport = this._adjustCellsAroundViewport( props, state.cellsAroundViewport, + state.pendingScrollUpdateCount, ); const renderMask = VirtualizedList._createRenderMask( props, @@ -1788,7 +1893,7 @@ class VirtualizedList extends StateSafePureComponent { return { index, item, - key: this._keyExtractor(item, index, props), + key: VirtualizedList._keyExtractor(item, index, props), isViewable, }; }; @@ -1849,13 +1954,12 @@ class VirtualizedList extends StateSafePureComponent { inLayout?: boolean, ... } => { - const {data, getItem, getItemCount, getItemLayout} = props; + const {data, getItemCount, getItemLayout} = props; invariant( index >= 0 && index < getItemCount(data), 'Tried to get frame for out of range index ' + index, ); - const item = getItem(data, index); - const frame = this._frames[this._keyExtractor(item, index, props)]; + const frame = this._frames[VirtualizedList._getItemKey(props, index)]; if (!frame || frame.index !== index) { if (getItemLayout) { /* $FlowFixMe[prop-missing] (>=0.63.0 site=react_native_fb) This comment @@ -1890,11 +1994,8 @@ class VirtualizedList extends StateSafePureComponent { // where it is. if ( focusedCellIndex >= itemCount || - this._keyExtractor( - props.getItem(props.data, focusedCellIndex), - focusedCellIndex, - props, - ) !== this._lastFocusedCellKey + VirtualizedList._getItemKey(props, focusedCellIndex) !== + this._lastFocusedCellKey ) { return []; } @@ -1935,6 +2036,11 @@ class VirtualizedList extends StateSafePureComponent { props: FrameMetricProps, cellsAroundViewport: {first: number, last: number}, ) { + // If we have any pending scroll updates it means that the scroll metrics + // are out of date and we should not call any of the visibility callbacks. + if (this.state.pendingScrollUpdateCount > 0) { + return; + } this._viewabilityTuples.forEach(tuple => { tuple.viewabilityHelper.onUpdate( props, diff --git a/packages/virtualized-lists/Lists/__tests__/VirtualizedList-test.js b/packages/virtualized-lists/Lists/__tests__/VirtualizedList-test.js index 218f7c1cae71db..c030e8a2d7e690 100644 --- a/packages/virtualized-lists/Lists/__tests__/VirtualizedList-test.js +++ b/packages/virtualized-lists/Lists/__tests__/VirtualizedList-test.js @@ -2164,10 +2164,70 @@ it('virtualizes away last focused index if item removed', () => { expect(component).toMatchSnapshot(); }); -function generateItems(count) { +it('handles maintainVisibleContentPosition', () => { + const items = generateItems(20); + const ITEM_HEIGHT = 10; + + let component; + ReactTestRenderer.act(() => { + component = ReactTestRenderer.create( + , + ); + }); + + ReactTestRenderer.act(() => { + simulateLayout(component, { + viewport: {width: 10, height: 50}, + content: {width: 10, height: items.length * ITEM_HEIGHT}, + }); + + performAllBatches(); + }); + + // Initial render. + expect(component).toMatchSnapshot(); + + // Add new items at the start of the list to trigger the maintainVisibleContentPosition adjustment. + const newItems = [...generateItems(10, items.length), ...items]; + ReactTestRenderer.act(() => { + component.update( + , + ); + }); + + // Previously rendered cells should be rendered still. + expect(component).toMatchSnapshot(); + + // Simulate scroll adjustment from native maintainVisibleContentPosition. + ReactTestRenderer.act(() => { + simulateContentLayout(component, { + width: 10, + height: newItems.length * ITEM_HEIGHT, + }); + simulateScroll(component, {x: 0, y: 10 * ITEM_HEIGHT}); + performAllBatches(); + }); + + // Previously rendered cells should be rendered still + starting to render new cells ahead. + expect(component).toMatchSnapshot(); +}); + +function generateItems(count, startKey = 0) { return Array(count) .fill() - .map((_, i) => ({key: i})); + .map((_, i) => ({key: i + startKey})); } function generateItemsStickyEveryN(count, n) { @@ -2225,7 +2285,7 @@ function simulateContentLayout(component, dimensions) { function simulateCellLayout(component, items, itemIndex, dimensions) { const instance = component.getInstance(); - const cellKey = instance._keyExtractor( + const cellKey = VirtualizedList._keyExtractor( items[itemIndex], itemIndex, instance.props, diff --git a/packages/virtualized-lists/Lists/__tests__/__snapshots__/VirtualizedList-test.js.snap b/packages/virtualized-lists/Lists/__tests__/__snapshots__/VirtualizedList-test.js.snap index b59f291eb6dbbe..b877e3da84d999 100644 --- a/packages/virtualized-lists/Lists/__tests__/__snapshots__/VirtualizedList-test.js.snap +++ b/packages/virtualized-lists/Lists/__tests__/__snapshots__/VirtualizedList-test.js.snap @@ -65,6 +65,7 @@ exports[`VirtualizedList forwards correct stickyHeaderIndices when ListHeaderCom >
@@ -1048,6 +1049,7 @@ exports[`VirtualizedList renders all the bells and whistles 1`] = `
@@ -1834,45 +1837,12 @@ exports[`adjusts render area with non-zero initialScrollIndex 1`] = ` /> - - - - - - - - - - - - - - + style={ + Object { + "height": 50, + } + } + /> `; @@ -3122,80 +3092,58 @@ exports[`gracefully handles negative initialScrollIndex 1`] = ` /> - - -`; - -exports[`initially renders nothing when initialNumToRender is 0 1`] = ` - - + onFocusCapture={[Function]} + style={null} + > + + + onFocusCapture={[Function]} + style={null} + > + + + + + + + + + + + + + + `; -exports[`keeps viewport above last focused rendered 1`] = ` +exports[`handles maintainVisibleContentPosition 1`] = ` - - - - - - - - - - - - - - - - - - - - - - - - `; -exports[`keeps viewport below last focused rendered 1`] = ` +exports[`handles maintainVisibleContentPosition 2`] = ` - + + + + + + + + + + + + + + + + + + +`; + +exports[`handles maintainVisibleContentPosition 3`] = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`initially renders nothing when initialNumToRender is 0 1`] = ` + + + + + +`; + +exports[`keeps viewport above last focused rendered 1`] = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`keeps viewport below last focused rendered 1`] = ` + + + + + + + @@ -4072,21 +4598,12 @@ exports[`renders new items when data is updated with non-zero initialScrollIndex /> - - - - - + style={ + Object { + "height": 20, + } + } + /> `; diff --git a/packages/virtualized-lists/Lists/__tests__/__snapshots__/VirtualizedSectionList-test.js.snap b/packages/virtualized-lists/Lists/__tests__/__snapshots__/VirtualizedSectionList-test.js.snap index 9f2d41bf9aa513..3f64c8856611e6 100644 --- a/packages/virtualized-lists/Lists/__tests__/__snapshots__/VirtualizedSectionList-test.js.snap +++ b/packages/virtualized-lists/Lists/__tests__/__snapshots__/VirtualizedSectionList-test.js.snap @@ -831,6 +831,7 @@ exports[`VirtualizedSectionList renders all the bells and whistles 1`] = `