@react-native/virtualized-lists 0.72.3 → 0.72.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Lists/VirtualizedList.js +152 -33
- package/package.json +1 -1
package/Lists/VirtualizedList.js
CHANGED
|
@@ -73,6 +73,10 @@ type ViewabilityHelperCallbackTuple = {
|
|
|
73
73
|
type State = {
|
|
74
74
|
renderMask: CellRenderMask,
|
|
75
75
|
cellsAroundViewport: {first: number, last: number},
|
|
76
|
+
// Used to track items added at the start of the list for maintainVisibleContentPosition.
|
|
77
|
+
firstVisibleItemKey: ?string,
|
|
78
|
+
// When > 0 the scroll position available in JS is considered stale and should not be used.
|
|
79
|
+
pendingScrollUpdateCount: number,
|
|
76
80
|
};
|
|
77
81
|
|
|
78
82
|
/**
|
|
@@ -448,9 +452,24 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
448
452
|
|
|
449
453
|
const initialRenderRegion = VirtualizedList._initialRenderRegion(props);
|
|
450
454
|
|
|
455
|
+
const minIndexForVisible =
|
|
456
|
+
this.props.maintainVisibleContentPosition?.minIndexForVisible ?? 0;
|
|
457
|
+
|
|
451
458
|
this.state = {
|
|
452
459
|
cellsAroundViewport: initialRenderRegion,
|
|
453
460
|
renderMask: VirtualizedList._createRenderMask(props, initialRenderRegion),
|
|
461
|
+
firstVisibleItemKey:
|
|
462
|
+
this.props.getItemCount(this.props.data) > minIndexForVisible
|
|
463
|
+
? VirtualizedList._getItemKey(this.props, minIndexForVisible)
|
|
464
|
+
: null,
|
|
465
|
+
// When we have a non-zero initialScrollIndex, we will receive a
|
|
466
|
+
// scroll event later so this will prevent the window from updating
|
|
467
|
+
// until we get a valid offset.
|
|
468
|
+
pendingScrollUpdateCount:
|
|
469
|
+
this.props.initialScrollIndex != null &&
|
|
470
|
+
this.props.initialScrollIndex > 0
|
|
471
|
+
? 1
|
|
472
|
+
: 0,
|
|
454
473
|
};
|
|
455
474
|
}
|
|
456
475
|
|
|
@@ -502,6 +521,40 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
502
521
|
}
|
|
503
522
|
}
|
|
504
523
|
|
|
524
|
+
static _findItemIndexWithKey(
|
|
525
|
+
props: Props,
|
|
526
|
+
key: string,
|
|
527
|
+
hint: ?number,
|
|
528
|
+
): ?number {
|
|
529
|
+
const itemCount = props.getItemCount(props.data);
|
|
530
|
+
if (hint != null && hint >= 0 && hint < itemCount) {
|
|
531
|
+
const curKey = VirtualizedList._getItemKey(props, hint);
|
|
532
|
+
if (curKey === key) {
|
|
533
|
+
return hint;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
for (let ii = 0; ii < itemCount; ii++) {
|
|
537
|
+
const curKey = VirtualizedList._getItemKey(props, ii);
|
|
538
|
+
if (curKey === key) {
|
|
539
|
+
return ii;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
static _getItemKey(
|
|
546
|
+
props: {
|
|
547
|
+
data: Props['data'],
|
|
548
|
+
getItem: Props['getItem'],
|
|
549
|
+
keyExtractor: Props['keyExtractor'],
|
|
550
|
+
...
|
|
551
|
+
},
|
|
552
|
+
index: number,
|
|
553
|
+
): string {
|
|
554
|
+
const item = props.getItem(props.data, index);
|
|
555
|
+
return VirtualizedList._keyExtractor(item, index, props);
|
|
556
|
+
}
|
|
557
|
+
|
|
505
558
|
static _createRenderMask(
|
|
506
559
|
props: Props,
|
|
507
560
|
cellsAroundViewport: {first: number, last: number},
|
|
@@ -585,6 +638,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
585
638
|
_adjustCellsAroundViewport(
|
|
586
639
|
props: Props,
|
|
587
640
|
cellsAroundViewport: {first: number, last: number},
|
|
641
|
+
pendingScrollUpdateCount: number,
|
|
588
642
|
): {first: number, last: number} {
|
|
589
643
|
const {data, getItemCount} = props;
|
|
590
644
|
const onEndReachedThreshold = onEndReachedThresholdOrDefault(
|
|
@@ -616,21 +670,9 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
616
670
|
),
|
|
617
671
|
};
|
|
618
672
|
} else {
|
|
619
|
-
// If we have a
|
|
620
|
-
//
|
|
621
|
-
|
|
622
|
-
// we will trust the initialScrollIndex suggestion.
|
|
623
|
-
|
|
624
|
-
// Thus, we want to recalculate the windowed render limits if any of the following hold:
|
|
625
|
-
// - initialScrollIndex is undefined or is 0
|
|
626
|
-
// - initialScrollIndex > 0 AND scrolling is complete
|
|
627
|
-
// - initialScrollIndex > 0 AND the end of the list is visible (this handles the case
|
|
628
|
-
// where the list is shorter than the visible area)
|
|
629
|
-
if (
|
|
630
|
-
props.initialScrollIndex &&
|
|
631
|
-
!this._scrollMetrics.offset &&
|
|
632
|
-
Math.abs(distanceFromEnd) >= Number.EPSILON
|
|
633
|
-
) {
|
|
673
|
+
// If we have a pending scroll update, we should not adjust the render window as it
|
|
674
|
+
// might override the correct window.
|
|
675
|
+
if (pendingScrollUpdateCount > 0) {
|
|
634
676
|
return cellsAroundViewport.last >= getItemCount(data)
|
|
635
677
|
? VirtualizedList._constrainToItemCount(cellsAroundViewport, props)
|
|
636
678
|
: cellsAroundViewport;
|
|
@@ -712,14 +754,59 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
712
754
|
return prevState;
|
|
713
755
|
}
|
|
714
756
|
|
|
757
|
+
let maintainVisibleContentPositionAdjustment: ?number = null;
|
|
758
|
+
const prevFirstVisibleItemKey = prevState.firstVisibleItemKey;
|
|
759
|
+
const minIndexForVisible =
|
|
760
|
+
newProps.maintainVisibleContentPosition?.minIndexForVisible ?? 0;
|
|
761
|
+
const newFirstVisibleItemKey =
|
|
762
|
+
newProps.getItemCount(newProps.data) > minIndexForVisible
|
|
763
|
+
? VirtualizedList._getItemKey(newProps, minIndexForVisible)
|
|
764
|
+
: null;
|
|
765
|
+
if (
|
|
766
|
+
newProps.maintainVisibleContentPosition != null &&
|
|
767
|
+
prevFirstVisibleItemKey != null &&
|
|
768
|
+
newFirstVisibleItemKey != null
|
|
769
|
+
) {
|
|
770
|
+
if (newFirstVisibleItemKey !== prevFirstVisibleItemKey) {
|
|
771
|
+
// Fast path if items were added at the start of the list.
|
|
772
|
+
const hint =
|
|
773
|
+
itemCount - prevState.renderMask.numCells() + minIndexForVisible;
|
|
774
|
+
const firstVisibleItemIndex = VirtualizedList._findItemIndexWithKey(
|
|
775
|
+
newProps,
|
|
776
|
+
prevFirstVisibleItemKey,
|
|
777
|
+
hint,
|
|
778
|
+
);
|
|
779
|
+
maintainVisibleContentPositionAdjustment =
|
|
780
|
+
firstVisibleItemIndex != null
|
|
781
|
+
? firstVisibleItemIndex - minIndexForVisible
|
|
782
|
+
: null;
|
|
783
|
+
} else {
|
|
784
|
+
maintainVisibleContentPositionAdjustment = null;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
715
788
|
const constrainedCells = VirtualizedList._constrainToItemCount(
|
|
716
|
-
|
|
789
|
+
maintainVisibleContentPositionAdjustment != null
|
|
790
|
+
? {
|
|
791
|
+
first:
|
|
792
|
+
prevState.cellsAroundViewport.first +
|
|
793
|
+
maintainVisibleContentPositionAdjustment,
|
|
794
|
+
last:
|
|
795
|
+
prevState.cellsAroundViewport.last +
|
|
796
|
+
maintainVisibleContentPositionAdjustment,
|
|
797
|
+
}
|
|
798
|
+
: prevState.cellsAroundViewport,
|
|
717
799
|
newProps,
|
|
718
800
|
);
|
|
719
801
|
|
|
720
802
|
return {
|
|
721
803
|
cellsAroundViewport: constrainedCells,
|
|
722
804
|
renderMask: VirtualizedList._createRenderMask(newProps, constrainedCells),
|
|
805
|
+
firstVisibleItemKey: newFirstVisibleItemKey,
|
|
806
|
+
pendingScrollUpdateCount:
|
|
807
|
+
maintainVisibleContentPositionAdjustment != null
|
|
808
|
+
? prevState.pendingScrollUpdateCount + 1
|
|
809
|
+
: prevState.pendingScrollUpdateCount,
|
|
723
810
|
};
|
|
724
811
|
}
|
|
725
812
|
|
|
@@ -751,7 +838,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
751
838
|
|
|
752
839
|
for (let ii = first; ii <= last; ii++) {
|
|
753
840
|
const item = getItem(data, ii);
|
|
754
|
-
const key =
|
|
841
|
+
const key = VirtualizedList._keyExtractor(item, ii, this.props);
|
|
755
842
|
|
|
756
843
|
this._indicesToKeys.set(ii, key);
|
|
757
844
|
if (stickyIndicesFromProps.has(ii + stickyOffset)) {
|
|
@@ -824,15 +911,14 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
824
911
|
_getSpacerKey = (isVertical: boolean): string =>
|
|
825
912
|
isVertical ? 'height' : 'width';
|
|
826
913
|
|
|
827
|
-
_keyExtractor(
|
|
914
|
+
static _keyExtractor(
|
|
828
915
|
item: Item,
|
|
829
916
|
index: number,
|
|
830
917
|
props: {
|
|
831
918
|
keyExtractor?: ?(item: Item, index: number) => string,
|
|
832
919
|
...
|
|
833
920
|
},
|
|
834
|
-
|
|
835
|
-
) {
|
|
921
|
+
): string {
|
|
836
922
|
if (props.keyExtractor != null) {
|
|
837
923
|
return props.keyExtractor(item, index);
|
|
838
924
|
}
|
|
@@ -878,6 +964,10 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
878
964
|
cellKey={this._getCellKey() + '-header'}
|
|
879
965
|
key="$header">
|
|
880
966
|
<View
|
|
967
|
+
// We expect that header component will be a single native view so make it
|
|
968
|
+
// not collapsable to avoid this view being flattened and make this assumption
|
|
969
|
+
// no longer true.
|
|
970
|
+
collapsable={false}
|
|
881
971
|
onLayout={this._onLayoutHeader}
|
|
882
972
|
style={StyleSheet.compose(
|
|
883
973
|
inversionStyle,
|
|
@@ -1035,6 +1125,16 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1035
1125
|
style: inversionStyle
|
|
1036
1126
|
? [inversionStyle, this.props.style]
|
|
1037
1127
|
: this.props.style,
|
|
1128
|
+
maintainVisibleContentPosition:
|
|
1129
|
+
this.props.maintainVisibleContentPosition != null
|
|
1130
|
+
? {
|
|
1131
|
+
...this.props.maintainVisibleContentPosition,
|
|
1132
|
+
// Adjust index to account for ListHeaderComponent.
|
|
1133
|
+
minIndexForVisible:
|
|
1134
|
+
this.props.maintainVisibleContentPosition.minIndexForVisible +
|
|
1135
|
+
(this.props.ListHeaderComponent ? 1 : 0),
|
|
1136
|
+
}
|
|
1137
|
+
: undefined,
|
|
1038
1138
|
};
|
|
1039
1139
|
|
|
1040
1140
|
this._hasMore = this.state.cellsAroundViewport.last < itemCount - 1;
|
|
@@ -1456,8 +1556,13 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1456
1556
|
onStartReachedThreshold,
|
|
1457
1557
|
onEndReached,
|
|
1458
1558
|
onEndReachedThreshold,
|
|
1459
|
-
initialScrollIndex,
|
|
1460
1559
|
} = this.props;
|
|
1560
|
+
// If we have any pending scroll updates it means that the scroll metrics
|
|
1561
|
+
// are out of date and we should not call any of the edge reached callbacks.
|
|
1562
|
+
if (this.state.pendingScrollUpdateCount > 0) {
|
|
1563
|
+
return;
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1461
1566
|
const {contentLength, visibleLength, offset} = this._scrollMetrics;
|
|
1462
1567
|
let distanceFromStart = offset;
|
|
1463
1568
|
let distanceFromEnd = contentLength - visibleLength - offset;
|
|
@@ -1509,14 +1614,8 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1509
1614
|
isWithinStartThreshold &&
|
|
1510
1615
|
this._scrollMetrics.contentLength !== this._sentStartForContentLength
|
|
1511
1616
|
) {
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
// timestamp to differentiate between the initial scroll metrics and when we actually
|
|
1515
|
-
// received the first scroll event.
|
|
1516
|
-
if (!initialScrollIndex || this._scrollMetrics.timestamp !== 0) {
|
|
1517
|
-
this._sentStartForContentLength = this._scrollMetrics.contentLength;
|
|
1518
|
-
onStartReached({distanceFromStart});
|
|
1519
|
-
}
|
|
1617
|
+
this._sentStartForContentLength = this._scrollMetrics.contentLength;
|
|
1618
|
+
onStartReached({distanceFromStart});
|
|
1520
1619
|
}
|
|
1521
1620
|
|
|
1522
1621
|
// If the user scrolls away from the start or end and back again,
|
|
@@ -1643,6 +1742,11 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1643
1742
|
visibleLength,
|
|
1644
1743
|
zoomScale,
|
|
1645
1744
|
};
|
|
1745
|
+
if (this.state.pendingScrollUpdateCount > 0) {
|
|
1746
|
+
this.setState(state => ({
|
|
1747
|
+
pendingScrollUpdateCount: state.pendingScrollUpdateCount - 1,
|
|
1748
|
+
}));
|
|
1749
|
+
}
|
|
1646
1750
|
this._updateViewableItems(this.props, this.state.cellsAroundViewport);
|
|
1647
1751
|
if (!this.props) {
|
|
1648
1752
|
return;
|
|
@@ -1758,6 +1862,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1758
1862
|
const cellsAroundViewport = this._adjustCellsAroundViewport(
|
|
1759
1863
|
props,
|
|
1760
1864
|
state.cellsAroundViewport,
|
|
1865
|
+
state.pendingScrollUpdateCount,
|
|
1761
1866
|
);
|
|
1762
1867
|
const renderMask = VirtualizedList._createRenderMask(
|
|
1763
1868
|
props,
|
|
@@ -1788,7 +1893,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1788
1893
|
return {
|
|
1789
1894
|
index,
|
|
1790
1895
|
item,
|
|
1791
|
-
key:
|
|
1896
|
+
key: VirtualizedList._keyExtractor(item, index, props),
|
|
1792
1897
|
isViewable,
|
|
1793
1898
|
};
|
|
1794
1899
|
};
|
|
@@ -1849,13 +1954,12 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1849
1954
|
inLayout?: boolean,
|
|
1850
1955
|
...
|
|
1851
1956
|
} => {
|
|
1852
|
-
const {data,
|
|
1957
|
+
const {data, getItemCount, getItemLayout} = props;
|
|
1853
1958
|
invariant(
|
|
1854
1959
|
index >= 0 && index < getItemCount(data),
|
|
1855
1960
|
'Tried to get frame for out of range index ' + index,
|
|
1856
1961
|
);
|
|
1857
|
-
const
|
|
1858
|
-
const frame = this._frames[this._keyExtractor(item, index, props)];
|
|
1962
|
+
const frame = this._frames[VirtualizedList._getItemKey(props, index)];
|
|
1859
1963
|
if (!frame || frame.index !== index) {
|
|
1860
1964
|
if (getItemLayout) {
|
|
1861
1965
|
/* $FlowFixMe[prop-missing] (>=0.63.0 site=react_native_fb) This comment
|
|
@@ -1886,6 +1990,16 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1886
1990
|
const focusedCellIndex = lastFocusedCellRenderer.props.index;
|
|
1887
1991
|
const itemCount = props.getItemCount(props.data);
|
|
1888
1992
|
|
|
1993
|
+
// The last cell we rendered may be at a new index. Bail if we don't know
|
|
1994
|
+
// where it is.
|
|
1995
|
+
if (
|
|
1996
|
+
focusedCellIndex >= itemCount ||
|
|
1997
|
+
VirtualizedList._getItemKey(props, focusedCellIndex) !==
|
|
1998
|
+
this._lastFocusedCellKey
|
|
1999
|
+
) {
|
|
2000
|
+
return [];
|
|
2001
|
+
}
|
|
2002
|
+
|
|
1889
2003
|
let first = focusedCellIndex;
|
|
1890
2004
|
let heightOfCellsBeforeFocused = 0;
|
|
1891
2005
|
for (
|
|
@@ -1922,6 +2036,11 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
|
|
|
1922
2036
|
props: FrameMetricProps,
|
|
1923
2037
|
cellsAroundViewport: {first: number, last: number},
|
|
1924
2038
|
) {
|
|
2039
|
+
// If we have any pending scroll updates it means that the scroll metrics
|
|
2040
|
+
// are out of date and we should not call any of the visibility callbacks.
|
|
2041
|
+
if (this.state.pendingScrollUpdateCount > 0) {
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
1925
2044
|
this._viewabilityTuples.forEach(tuple => {
|
|
1926
2045
|
tuple.viewabilityHelper.onUpdate(
|
|
1927
2046
|
props,
|