@@ -755,7 +755,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
755
755
static _createRenderMask (
756
756
props : Props ,
757
757
cellsAroundViewport : { first : number , last : number } ,
758
- lastFocusedItem : ?number ,
758
+ additionalRegions ? : ?$ReadOnlyArray < { first : number , last : number } > ,
759
759
) : CellRenderMask {
760
760
const itemCount = props . getItemCount ( props . data ) ;
761
761
@@ -768,17 +768,12 @@ class VirtualizedList extends React.PureComponent<Props, State> {
768
768
769
769
const renderMask = new CellRenderMask ( itemCount ) ;
770
770
771
- // Keep the items around the last focused rendered, to allow for keyboard
772
- // navigation
773
- if ( lastFocusedItem ) {
774
- const first = Math . max ( 0 , lastFocusedItem - 1 ) ;
775
- const last = Math . min ( itemCount - 1 , lastFocusedItem + 1 ) ;
776
- renderMask . addCells ( { first, last} ) ;
777
- }
778
-
779
771
if ( itemCount > 0 ) {
780
- if ( cellsAroundViewport . last >= cellsAroundViewport . first ) {
781
- renderMask . addCells ( cellsAroundViewport ) ;
772
+ const allRegions = [ cellsAroundViewport , ...( additionalRegions ?? [ ] ) ] ;
773
+ for ( const region of allRegions ) {
774
+ if ( region . last >= region . first ) {
775
+ renderMask . addCells ( region ) ;
776
+ }
782
777
}
783
778
784
779
// The initially rendered cells are retained as part of the
@@ -1016,7 +1011,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
1016
1011
prevCellKey = { prevCellKey }
1017
1012
onUpdateSeparators = { this . _onUpdateSeparators }
1018
1013
onLayout = { e => this . _onCellLayout ( e , key , ii ) }
1019
- onFocusCapture = { e => this . _onCellFocusCapture ( ii ) }
1014
+ onFocusCapture = { e => this . _onCellFocusCapture ( key ) }
1020
1015
onUnmount = { this . _onCellUnmount }
1021
1016
parentProps = { this . props }
1022
1017
ref = { ref => {
@@ -1364,7 +1359,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
1364
1359
_averageCellLength = 0 ;
1365
1360
// Maps a cell key to the set of keys for all outermost child lists within that cell
1366
1361
_cellKeysToChildListKeys : Map < string , Set < string > > = new Map ( ) ;
1367
- _cellRefs = { } ;
1362
+ _cellRefs : { [ string ] : ? CellRenderer } = { } ;
1368
1363
_fillRateHelper: FillRateHelper;
1369
1364
_frames = { } ;
1370
1365
_footerLength = 0;
@@ -1376,7 +1371,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
1376
1371
_hiPriInProgress: boolean = false; // flag to prevent infinite hiPri cell limit update
1377
1372
_highestMeasuredFrameIndex = 0;
1378
1373
_indicesToKeys: Map< number , string > = new Map();
1379
- _lastFocusedItem : ?number = null;
1374
+ _lastFocusedCellKey : ?string = null;
1380
1375
_nestedChildLists: Map<
1381
1376
string ,
1382
1377
{
@@ -1485,12 +1480,12 @@ class VirtualizedList extends React.PureComponent<Props, State> {
1485
1480
this._updateViewableItems(this.props.data);
1486
1481
}
1487
1482
1488
- _onCellFocusCapture ( itemIndex : number ) {
1489
- this . _lastFocusedItem = itemIndex ;
1483
+ _onCellFocusCapture ( cellKey : string ) {
1484
+ this . _lastFocusedCellKey = cellKey ;
1490
1485
const renderMask = VirtualizedList . _createRenderMask (
1491
1486
this . props ,
1492
1487
this . state . cellsAroundViewport ,
1493
- this . _lastFocusedItem ,
1488
+ this . _getNonViewportRenderRegions ( ) ,
1494
1489
) ;
1495
1490
1496
1491
if ( ! renderMask . equals ( this . state . renderMask ) ) {
@@ -1847,7 +1842,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
1847
1842
}
1848
1843
// Mark as high priority if we're close to the end of the last item
1849
1844
// But only if there are items after the last rendered item
1850
- if (last < itemCount - 1 ) {
1845
+ if (last > 0 && last < itemCount - 1 ) {
1851
1846
const distBottom =
1852
1847
this . _getFrameMetricsApprox ( last ) . offset - ( offset + visibleLength ) ;
1853
1848
hiPri =
@@ -1926,7 +1921,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
1926
1921
const renderMask = VirtualizedList . _createRenderMask (
1927
1922
props ,
1928
1923
cellsAroundViewport ,
1929
- this . _lastFocusedItem ,
1924
+ this . _getNonViewportRenderRegions ( ) ,
1930
1925
) ;
1931
1926
1932
1927
if (
@@ -1959,7 +1954,11 @@ class VirtualizedList extends React.PureComponent<Props, State> {
1959
1954
// check for invalid frames due to row re-ordering
1960
1955
return frame ;
1961
1956
} else {
1962
- const { getItemLayout } = this . props ;
1957
+ const { data , getItemCount, getItemLayout} = this . props ;
1958
+ invariant (
1959
+ index >= 0 && index < getItemCount ( data ) ,
1960
+ 'Tried to get frame for out of range index ' + index ,
1961
+ ) ;
1963
1962
invariant (
1964
1963
! getItemLayout ,
1965
1964
'Should not have to estimate frames when a measurement metrics function is provided' ,
@@ -1982,7 +1981,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
1982
1981
} => {
1983
1982
const { data, getItem, getItemCount, getItemLayout} = this . props ;
1984
1983
invariant (
1985
- getItemCount ( data ) > index ,
1984
+ index >= 0 && index < getItemCount ( data ) ,
1986
1985
'Tried to get frame for out of range index ' + index ,
1987
1986
) ;
1988
1987
const item = getItem ( data , index ) ;
@@ -1998,6 +1997,57 @@ class VirtualizedList extends React.PureComponent<Props, State> {
1998
1997
return frame ;
1999
1998
} ;
2000
1999
2000
+ _getNonViewportRenderRegions = ( ) : $ReadOnlyArray < {
2001
+ first : number ,
2002
+ last : number ,
2003
+ } > => {
2004
+ // Keep a viewport's worth of content around the last focused cell to allow
2005
+ // random navigation around it without any blanking. E.g. tabbing from one
2006
+ // focused item out of viewport to another.
2007
+ if (
2008
+ ! ( this . _lastFocusedCellKey && this . _cellRefs [ this . _lastFocusedCellKey ] )
2009
+ ) {
2010
+ return [ ] ;
2011
+ }
2012
+
2013
+ const lastFocusedCellRenderer = this . _cellRefs [ this . _lastFocusedCellKey ] ;
2014
+ const focusedCellIndex = lastFocusedCellRenderer . props . index ;
2015
+ const itemCount = this . props . getItemCount ( this . props . data ) ;
2016
+
2017
+ // The cell may have been unmounted and have a stale index
2018
+ if (
2019
+ focusedCellIndex >= itemCount ||
2020
+ this . _indicesToKeys . get ( focusedCellIndex ) !== this . _lastFocusedCellKey
2021
+ ) {
2022
+ return [ ] ;
2023
+ }
2024
+
2025
+ let first = focusedCellIndex ;
2026
+ let heightOfCellsBeforeFocused = 0 ;
2027
+ for (
2028
+ let i = first - 1 ;
2029
+ i >= 0 && heightOfCellsBeforeFocused < this . _scrollMetrics . visibleLength ;
2030
+ i --
2031
+ ) {
2032
+ first -- ;
2033
+ heightOfCellsBeforeFocused += this . _getFrameMetricsApprox ( i ) . length ;
2034
+ }
2035
+
2036
+ let last = focusedCellIndex ;
2037
+ let heightOfCellsAfterFocused = 0 ;
2038
+ for (
2039
+ let i = last + 1 ;
2040
+ i < itemCount &&
2041
+ heightOfCellsAfterFocused < this . _scrollMetrics . visibleLength ;
2042
+ i ++
2043
+ ) {
2044
+ last ++ ;
2045
+ heightOfCellsAfterFocused += this . _getFrameMetricsApprox ( i ) . length ;
2046
+ }
2047
+
2048
+ return [ { first , last } ] ;
2049
+ } ;
2050
+
2001
2051
_updateViewableItems ( data : any ) {
2002
2052
const { getItemCount} = this . props ;
2003
2053
0 commit comments