@react-native/virtualized-lists 0.73.0 → 0.73.1

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.
@@ -10,7 +10,8 @@
10
10
 
11
11
  'use strict';
12
12
 
13
- import type {FrameMetricProps} from './VirtualizedListProps';
13
+ import type {CellMetricProps} from './ListMetricsAggregator';
14
+ import ListMetricsAggregator from './ListMetricsAggregator';
14
15
 
15
16
  export type FillRateInfo = Info;
16
17
 
@@ -27,13 +28,6 @@ class Info {
27
28
  sample_count: number = 0;
28
29
  }
29
30
 
30
- type FrameMetrics = {
31
- inLayout?: boolean,
32
- length: number,
33
- offset: number,
34
- ...
35
- };
36
-
37
31
  const DEBUG = false;
38
32
 
39
33
  let _listeners: Array<(Info) => void> = [];
@@ -51,7 +45,7 @@ let _sampleRate = DEBUG ? 1 : null;
51
45
  class FillRateHelper {
52
46
  _anyBlankStartTime: ?number = null;
53
47
  _enabled = false;
54
- _getFrameMetrics: (index: number, props: FrameMetricProps) => ?FrameMetrics;
48
+ _listMetrics: ListMetricsAggregator;
55
49
  _info: Info = new Info();
56
50
  _mostlyBlankStartTime: ?number = null;
57
51
  _samplesStartTime: ?number = null;
@@ -79,10 +73,8 @@ class FillRateHelper {
79
73
  _minSampleCount = minSampleCount;
80
74
  }
81
75
 
82
- constructor(
83
- getFrameMetrics: (index: number, props: FrameMetricProps) => ?FrameMetrics,
84
- ) {
85
- this._getFrameMetrics = getFrameMetrics;
76
+ constructor(listMetrics: ListMetricsAggregator) {
77
+ this._listMetrics = listMetrics;
86
78
  this._enabled = (_sampleRate || 0) > Math.random();
87
79
  this._resetData();
88
80
  }
@@ -139,7 +131,7 @@ class FillRateHelper {
139
131
 
140
132
  computeBlankness(
141
133
  props: {
142
- ...FrameMetricProps,
134
+ ...CellMetricProps,
143
135
  initialNumToRender?: ?number,
144
136
  ...
145
137
  },
@@ -186,12 +178,12 @@ class FillRateHelper {
186
178
 
187
179
  let blankTop = 0;
188
180
  let first = cellsAroundViewport.first;
189
- let firstFrame = this._getFrameMetrics(first, props);
181
+ let firstFrame = this._listMetrics.getCellMetrics(first, props);
190
182
  while (
191
183
  first <= cellsAroundViewport.last &&
192
- (!firstFrame || !firstFrame.inLayout)
184
+ (!firstFrame || !firstFrame.isMounted)
193
185
  ) {
194
- firstFrame = this._getFrameMetrics(first, props);
186
+ firstFrame = this._listMetrics.getCellMetrics(first, props);
195
187
  first++;
196
188
  }
197
189
  // Only count blankTop if we aren't rendering the first item, otherwise we will count the header
@@ -204,12 +196,12 @@ class FillRateHelper {
204
196
  }
205
197
  let blankBottom = 0;
206
198
  let last = cellsAroundViewport.last;
207
- let lastFrame = this._getFrameMetrics(last, props);
199
+ let lastFrame = this._listMetrics.getCellMetrics(last, props);
208
200
  while (
209
201
  last >= cellsAroundViewport.first &&
210
- (!lastFrame || !lastFrame.inLayout)
202
+ (!lastFrame || !lastFrame.isMounted)
211
203
  ) {
212
- lastFrame = this._getFrameMetrics(last, props);
204
+ lastFrame = this._listMetrics.getCellMetrics(last, props);
213
205
  last--;
214
206
  }
215
207
  // Only count blankBottom if we aren't rendering the last item, otherwise we will count the
@@ -0,0 +1,385 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow strict-local
8
+ * @format
9
+ */
10
+
11
+ import type {Layout} from 'react-native/Libraries/Types/CoreEventTypes';
12
+ import type {Props as VirtualizedListProps} from './VirtualizedListProps';
13
+ import {keyExtractor as defaultKeyExtractor} from './VirtualizeUtils';
14
+
15
+ import invariant from 'invariant';
16
+
17
+ type LayoutEventDirection = 'top-down' | 'bottom-up';
18
+
19
+ export type CellMetrics = {
20
+ /**
21
+ * Index of the item in the list
22
+ */
23
+ index: number,
24
+ /**
25
+ * Length of the cell along the scrolling axis
26
+ */
27
+ length: number,
28
+ /**
29
+ * Distance between this cell and the start of the list along the scrolling
30
+ * axis
31
+ */
32
+ offset: number,
33
+ /**
34
+ * Whether the cell is last known to be mounted
35
+ */
36
+ isMounted: boolean,
37
+ };
38
+
39
+ // TODO: `inverted` can be incorporated here if it is moved to an order
40
+ // based implementation instead of transform.
41
+ export type ListOrientation = {
42
+ horizontal: boolean,
43
+ rtl: boolean,
44
+ };
45
+
46
+ /**
47
+ * Subset of VirtualizedList props needed to calculate cell metrics
48
+ */
49
+ export type CellMetricProps = {
50
+ data: VirtualizedListProps['data'],
51
+ getItemCount: VirtualizedListProps['getItemCount'],
52
+ getItem: VirtualizedListProps['getItem'],
53
+ getItemLayout?: VirtualizedListProps['getItemLayout'],
54
+ keyExtractor?: VirtualizedListProps['keyExtractor'],
55
+ ...
56
+ };
57
+
58
+ type UnresolvedCellMetrics = {
59
+ index: number,
60
+ layout: Layout,
61
+ isMounted: boolean,
62
+
63
+ // The length of list content at the time of layout is needed to correctly
64
+ // resolve flow relative offset in RTL. We are lazily notified of this after
65
+ // the layout of the cell, unless the cell relayout does not cause a length
66
+ // change. To keep stability, we use content length at time of query, or
67
+ // unmount if never queried.
68
+ listContentLength?: ?number,
69
+ };
70
+
71
+ /**
72
+ * Provides an interface to query information about the metrics of a list and its cells.
73
+ */
74
+ export default class ListMetricsAggregator {
75
+ _averageCellLength = 0;
76
+ _cellMetrics: Map<string, UnresolvedCellMetrics> = new Map();
77
+ _contentLength: ?number;
78
+ _highestMeasuredCellIndex = 0;
79
+ _measuredCellsLength = 0;
80
+ _measuredCellsCount = 0;
81
+ _orientation: ListOrientation = {
82
+ horizontal: false,
83
+ rtl: false,
84
+ };
85
+
86
+ // Fabric and Paper may call onLayout in different orders. We can tell which
87
+ // direction layout events happen on the first layout.
88
+ _onLayoutDirection: LayoutEventDirection = 'top-down';
89
+
90
+ /**
91
+ * Notify the ListMetricsAggregator that a cell has been laid out.
92
+ *
93
+ * @returns whether the cell layout has changed since last notification
94
+ */
95
+ notifyCellLayout({
96
+ cellIndex,
97
+ cellKey,
98
+ orientation,
99
+ layout,
100
+ }: {
101
+ cellIndex: number,
102
+ cellKey: string,
103
+ orientation: ListOrientation,
104
+ layout: Layout,
105
+ }): boolean {
106
+ if (this._contentLength == null) {
107
+ this._onLayoutDirection = 'bottom-up';
108
+ }
109
+
110
+ this._invalidateIfOrientationChanged(orientation);
111
+
112
+ // If layout is top-down, our most recently cached content length
113
+ // corresponds to this cell. Otherwise, we need to resolve when events fire
114
+ // up the tree to the new length.
115
+ const listContentLength =
116
+ this._onLayoutDirection === 'top-down' ? this._contentLength : null;
117
+
118
+ const next: UnresolvedCellMetrics = {
119
+ index: cellIndex,
120
+ layout: layout,
121
+ isMounted: true,
122
+ listContentLength,
123
+ };
124
+ const curr = this._cellMetrics.get(cellKey);
125
+
126
+ if (
127
+ !curr ||
128
+ this._selectOffset(next.layout) !== this._selectOffset(curr.layout) ||
129
+ this._selectLength(next.layout) !== this._selectLength(curr.layout) ||
130
+ (curr.listContentLength != null &&
131
+ curr.listContentLength !== this._contentLength)
132
+ ) {
133
+ if (curr) {
134
+ const dLength =
135
+ this._selectLength(next.layout) - this._selectLength(curr.layout);
136
+ this._measuredCellsLength += dLength;
137
+ } else {
138
+ this._measuredCellsLength += this._selectLength(next.layout);
139
+ this._measuredCellsCount += 1;
140
+ }
141
+
142
+ this._averageCellLength =
143
+ this._measuredCellsLength / this._measuredCellsCount;
144
+ this._cellMetrics.set(cellKey, next);
145
+ this._highestMeasuredCellIndex = Math.max(
146
+ this._highestMeasuredCellIndex,
147
+ cellIndex,
148
+ );
149
+ return true;
150
+ } else {
151
+ curr.isMounted = true;
152
+ return false;
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Notify ListMetricsAggregator that a cell has been unmounted.
158
+ */
159
+ notifyCellUnmounted(cellKey: string): void {
160
+ const curr = this._cellMetrics.get(cellKey);
161
+ if (curr) {
162
+ curr.isMounted = false;
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Notify ListMetricsAggregator that the lists content container has been laid out.
168
+ */
169
+ notifyListContentLayout({
170
+ orientation,
171
+ layout,
172
+ }: {
173
+ orientation: ListOrientation,
174
+ layout: $ReadOnly<{width: number, height: number}>,
175
+ }): void {
176
+ this._invalidateIfOrientationChanged(orientation);
177
+ const newLength = this._selectLength(layout);
178
+
179
+ // Fill in any just-measured cells which did not have this length available.
180
+ // This logic assumes that cell relayout will always change list content
181
+ // size, which isn't strictly correct, but issues should be rare and only
182
+ // on Paper.
183
+ if (this._onLayoutDirection === 'bottom-up') {
184
+ for (const cellMetric of this._cellMetrics.values()) {
185
+ if (cellMetric.listContentLength == null) {
186
+ cellMetric.listContentLength = newLength;
187
+ }
188
+ }
189
+ }
190
+
191
+ this._contentLength = newLength;
192
+ }
193
+
194
+ /**
195
+ * Return the average length of the cells which have been measured
196
+ */
197
+ getAverageCellLength(): number {
198
+ return this._averageCellLength;
199
+ }
200
+
201
+ /**
202
+ * Return the highest measured cell index (or 0 if nothing has been measured
203
+ * yet)
204
+ */
205
+ getHighestMeasuredCellIndex(): number {
206
+ return this._highestMeasuredCellIndex;
207
+ }
208
+
209
+ /**
210
+ * Returns the exact metrics of a cell if it has already been laid out,
211
+ * otherwise an estimate based on the average length of previously measured
212
+ * cells
213
+ */
214
+ getCellMetricsApprox(index: number, props: CellMetricProps): CellMetrics {
215
+ const frame = this.getCellMetrics(index, props);
216
+ if (frame && frame.index === index) {
217
+ // check for invalid frames due to row re-ordering
218
+ return frame;
219
+ } else {
220
+ const {data, getItemCount} = props;
221
+ invariant(
222
+ index >= 0 && index < getItemCount(data),
223
+ 'Tried to get frame for out of range index ' + index,
224
+ );
225
+ return {
226
+ length: this._averageCellLength,
227
+ offset: this._averageCellLength * index,
228
+ index,
229
+ isMounted: false,
230
+ };
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Returns the exact metrics of a cell if it has already been laid out
236
+ */
237
+ getCellMetrics(index: number, props: CellMetricProps): ?CellMetrics {
238
+ const {data, getItem, getItemCount, getItemLayout} = props;
239
+ invariant(
240
+ index >= 0 && index < getItemCount(data),
241
+ 'Tried to get metrics for out of range cell index ' + index,
242
+ );
243
+ const keyExtractor = props.keyExtractor ?? defaultKeyExtractor;
244
+ const frame = this._cellMetrics.get(
245
+ keyExtractor(getItem(data, index), index),
246
+ );
247
+ if (frame && frame.index === index) {
248
+ return this._resolveCellMetrics(frame);
249
+ }
250
+
251
+ if (getItemLayout) {
252
+ const {length, offset} = getItemLayout(data, index);
253
+ // TODO: `isMounted` is used for both "is exact layout" and "has been
254
+ // unmounted". Should be refactored.
255
+ return {index, length, offset, isMounted: true};
256
+ }
257
+
258
+ return null;
259
+ }
260
+
261
+ /**
262
+ * Gets an approximate offset to an item at a given index. Supports
263
+ * fractional indices.
264
+ */
265
+ getCellOffsetApprox(index: number, props: CellMetricProps): number {
266
+ if (Number.isInteger(index)) {
267
+ return this.getCellMetricsApprox(index, props).offset;
268
+ } else {
269
+ const frameMetrics = this.getCellMetricsApprox(Math.floor(index), props);
270
+ const remainder = index - Math.floor(index);
271
+ return frameMetrics.offset + remainder * frameMetrics.length;
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Returns the length of all ScrollView content along the scrolling axis.
277
+ */
278
+ getContentLength(): number {
279
+ return this._contentLength ?? 0;
280
+ }
281
+
282
+ /**
283
+ * Whether a content length has been observed
284
+ */
285
+ hasContentLength(): boolean {
286
+ return this._contentLength != null;
287
+ }
288
+
289
+ /**
290
+ * Whether the ListMetricsAggregator is notified of cell metrics before
291
+ * ScrollView metrics (bottom-up) or ScrollView metrics before cell metrics
292
+ * (top-down).
293
+ *
294
+ * Must be queried after cell layout
295
+ */
296
+ getLayoutEventDirection(): LayoutEventDirection {
297
+ return this._onLayoutDirection;
298
+ }
299
+
300
+ /**
301
+ * Whether the ListMetricsAggregator must be aware of the current length of
302
+ * ScrollView content to be able to correctly resolve the (flow-relative)
303
+ * metrics of a cell.
304
+ */
305
+ needsContentLengthForCellMetrics(): boolean {
306
+ return this._orientation.horizontal && this._orientation.rtl;
307
+ }
308
+
309
+ /**
310
+ * Finds the flow-relative offset (e.g. starting from the left in LTR, but
311
+ * right in RTL) from a layout box.
312
+ */
313
+ flowRelativeOffset(layout: Layout, referenceContentLength?: ?number): number {
314
+ const {horizontal, rtl} = this._orientation;
315
+
316
+ if (horizontal && rtl) {
317
+ const contentLength = referenceContentLength ?? this._contentLength;
318
+ invariant(
319
+ contentLength != null,
320
+ 'ListMetricsAggregator must be notified of list content layout before resolving offsets',
321
+ );
322
+ return (
323
+ contentLength -
324
+ (this._selectOffset(layout) + this._selectLength(layout))
325
+ );
326
+ } else {
327
+ return this._selectOffset(layout);
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Converts a flow-relative offset to a cartesian offset
333
+ */
334
+ cartesianOffset(flowRelativeOffset: number): number {
335
+ const {horizontal, rtl} = this._orientation;
336
+
337
+ if (horizontal && rtl) {
338
+ invariant(
339
+ this._contentLength != null,
340
+ 'ListMetricsAggregator must be notified of list content layout before resolving offsets',
341
+ );
342
+ return this._contentLength - flowRelativeOffset;
343
+ } else {
344
+ return flowRelativeOffset;
345
+ }
346
+ }
347
+
348
+ _invalidateIfOrientationChanged(orientation: ListOrientation): void {
349
+ if (orientation.rtl !== this._orientation.rtl) {
350
+ this._cellMetrics.clear();
351
+ }
352
+
353
+ if (orientation.horizontal !== this._orientation.horizontal) {
354
+ this._averageCellLength = 0;
355
+ this._contentLength = null;
356
+ this._highestMeasuredCellIndex = 0;
357
+ this._measuredCellsLength = 0;
358
+ this._measuredCellsCount = 0;
359
+ }
360
+
361
+ this._orientation = orientation;
362
+ }
363
+
364
+ _selectLength({
365
+ width,
366
+ height,
367
+ }: $ReadOnly<{width: number, height: number, ...}>): number {
368
+ return this._orientation.horizontal ? width : height;
369
+ }
370
+
371
+ _selectOffset({x, y}: $ReadOnly<{x: number, y: number, ...}>): number {
372
+ return this._orientation.horizontal ? x : y;
373
+ }
374
+
375
+ _resolveCellMetrics(metrics: UnresolvedCellMetrics): CellMetrics {
376
+ const {index, layout, isMounted, listContentLength} = metrics;
377
+
378
+ return {
379
+ index,
380
+ length: this._selectLength(layout),
381
+ isMounted,
382
+ offset: this.flowRelativeOffset(layout, listContentLength),
383
+ };
384
+ }
385
+ }
@@ -14,7 +14,7 @@ import * as React from 'react';
14
14
  /**
15
15
  * `setState` is called asynchronously, and should not rely on the value of
16
16
  * `this.props` or `this.state`:
17
- * https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
17
+ * https://react.dev/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
18
18
  *
19
19
  * SafePureComponent adds runtime enforcement, to catch cases where these
20
20
  * variables are read in a state updater function, instead of the ones passed
@@ -32,7 +32,7 @@ export default class StateSafePureComponent<
32
32
  }
33
33
 
34
34
  setState(
35
- partialState: ?($Shape<State> | ((State, Props) => ?$Shape<State>)),
35
+ partialState: ?(Partial<State> | ((State, Props) => ?Partial<State>)),
36
36
  callback?: () => mixed,
37
37
  ): void {
38
38
  if (typeof partialState === 'function') {
@@ -10,7 +10,8 @@
10
10
 
11
11
  'use strict';
12
12
 
13
- import type {FrameMetricProps} from './VirtualizedListProps';
13
+ import type {CellMetricProps} from './ListMetricsAggregator';
14
+ import ListMetricsAggregator from './ListMetricsAggregator';
14
15
 
15
16
  const invariant = require('invariant');
16
17
 
@@ -101,17 +102,10 @@ class ViewabilityHelper {
101
102
  * Determines which items are viewable based on the current metrics and config.
102
103
  */
103
104
  computeViewableItems(
104
- props: FrameMetricProps,
105
+ props: CellMetricProps,
105
106
  scrollOffset: number,
106
107
  viewportHeight: number,
107
- getFrameMetrics: (
108
- index: number,
109
- props: FrameMetricProps,
110
- ) => ?{
111
- length: number,
112
- offset: number,
113
- ...
114
- },
108
+ listMetrics: ListMetricsAggregator,
115
109
  // Optional optimization to reduce the scan size
116
110
  renderRange?: {
117
111
  first: number,
@@ -146,12 +140,13 @@ class ViewabilityHelper {
146
140
  return [];
147
141
  }
148
142
  for (let idx = first; idx <= last; idx++) {
149
- const metrics = getFrameMetrics(idx, props);
143
+ const metrics = listMetrics.getCellMetrics(idx, props);
150
144
  if (!metrics) {
151
145
  continue;
152
146
  }
153
- const top = metrics.offset - scrollOffset;
154
- const bottom = top + metrics.length;
147
+ const top = Math.floor(metrics.offset - scrollOffset);
148
+ const bottom = Math.floor(top + metrics.length);
149
+
155
150
  if (top < viewportHeight && bottom > 0) {
156
151
  firstVisible = idx;
157
152
  if (
@@ -178,21 +173,14 @@ class ViewabilityHelper {
178
173
  * `onViewableItemsChanged` as appropriate.
179
174
  */
180
175
  onUpdate(
181
- props: FrameMetricProps,
176
+ props: CellMetricProps,
182
177
  scrollOffset: number,
183
178
  viewportHeight: number,
184
- getFrameMetrics: (
185
- index: number,
186
- props: FrameMetricProps,
187
- ) => ?{
188
- length: number,
189
- offset: number,
190
- ...
191
- },
179
+ listMetrics: ListMetricsAggregator,
192
180
  createViewToken: (
193
181
  index: number,
194
182
  isViewable: boolean,
195
- props: FrameMetricProps,
183
+ props: CellMetricProps,
196
184
  ) => ViewToken,
197
185
  onViewableItemsChanged: ({
198
186
  viewableItems: Array<ViewToken>,
@@ -210,7 +198,7 @@ class ViewabilityHelper {
210
198
  if (
211
199
  (this._config.waitForInteraction && !this._hasInteracted) ||
212
200
  itemCount === 0 ||
213
- !getFrameMetrics(0, props)
201
+ !listMetrics.getCellMetrics(0, props)
214
202
  ) {
215
203
  return;
216
204
  }
@@ -220,7 +208,7 @@ class ViewabilityHelper {
220
208
  props,
221
209
  scrollOffset,
222
210
  viewportHeight,
223
- getFrameMetrics,
211
+ listMetrics,
224
212
  renderRange,
225
213
  );
226
214
  }
@@ -275,7 +263,7 @@ class ViewabilityHelper {
275
263
  }
276
264
 
277
265
  _onUpdateSync(
278
- props: FrameMetricProps,
266
+ props: CellMetricProps,
279
267
  viewableIndicesToCheck: Array<number>,
280
268
  onViewableItemsChanged: ({
281
269
  changed: Array<ViewToken>,
@@ -285,7 +273,7 @@ class ViewabilityHelper {
285
273
  createViewToken: (
286
274
  index: number,
287
275
  isViewable: boolean,
288
- props: FrameMetricProps,
276
+ props: CellMetricProps,
289
277
  ) => ViewToken,
290
278
  ) {
291
279
  // Filter out indices that have gone out of view since this call was scheduled.
@@ -10,7 +10,9 @@
10
10
 
11
11
  'use strict';
12
12
 
13
- import type {FrameMetricProps} from './VirtualizedListProps';
13
+ import type ListMetricsAggregator, {
14
+ CellMetricProps,
15
+ } from './ListMetricsAggregator';
14
16
 
15
17
  /**
16
18
  * Used to find the indices of the frames that overlap the given offsets. Useful for finding the
@@ -19,15 +21,8 @@ import type {FrameMetricProps} from './VirtualizedListProps';
19
21
  */
20
22
  export function elementsThatOverlapOffsets(
21
23
  offsets: Array<number>,
22
- props: FrameMetricProps,
23
- getFrameMetrics: (
24
- index: number,
25
- props: FrameMetricProps,
26
- ) => {
27
- length: number,
28
- offset: number,
29
- ...
30
- },
24
+ props: CellMetricProps,
25
+ listMetrics: ListMetricsAggregator,
31
26
  zoomScale: number = 1,
32
27
  ): Array<number> {
33
28
  const itemCount = props.getItemCount(props.data);
@@ -38,9 +33,8 @@ export function elementsThatOverlapOffsets(
38
33
  let right = itemCount - 1;
39
34
 
40
35
  while (left <= right) {
41
- // eslint-disable-next-line no-bitwise
42
- const mid = left + ((right - left) >>> 1);
43
- const frame = getFrameMetrics(mid, props);
36
+ const mid = left + Math.floor((right - left) / 2);
37
+ const frame = listMetrics.getCellMetricsApprox(mid, props);
44
38
  const scaledOffsetStart = frame.offset * zoomScale;
45
39
  const scaledOffsetEnd = (frame.offset + frame.length) * zoomScale;
46
40
 
@@ -99,21 +93,14 @@ export function newRangeCount(
99
93
  * biased in the direction of scroll.
100
94
  */
101
95
  export function computeWindowedRenderLimits(
102
- props: FrameMetricProps,
96
+ props: CellMetricProps,
103
97
  maxToRenderPerBatch: number,
104
98
  windowSize: number,
105
99
  prev: {
106
100
  first: number,
107
101
  last: number,
108
102
  },
109
- getFrameMetricsApprox: (
110
- index: number,
111
- props: FrameMetricProps,
112
- ) => {
113
- length: number,
114
- offset: number,
115
- ...
116
- },
103
+ listMetrics: ListMetricsAggregator,
117
104
  scrollMetrics: {
118
105
  dt: number,
119
106
  offset: number,
@@ -152,7 +139,7 @@ export function computeWindowedRenderLimits(
152
139
  const overscanEnd = Math.max(0, visibleEnd + leadFactor * overscanLength);
153
140
 
154
141
  const lastItemOffset =
155
- getFrameMetricsApprox(itemCount - 1, props).offset * zoomScale;
142
+ listMetrics.getCellMetricsApprox(itemCount - 1, props).offset * zoomScale;
156
143
  if (lastItemOffset < overscanBegin) {
157
144
  // Entire list is before our overscan window
158
145
  return {
@@ -165,7 +152,7 @@ export function computeWindowedRenderLimits(
165
152
  let [overscanFirst, first, last, overscanLast] = elementsThatOverlapOffsets(
166
153
  [overscanBegin, visibleBegin, visibleEnd, overscanEnd],
167
154
  props,
168
- getFrameMetricsApprox,
155
+ listMetrics,
169
156
  zoomScale,
170
157
  );
171
158
  overscanFirst = overscanFirst == null ? 0 : overscanFirst;
@@ -267,7 +267,7 @@ export interface VirtualizedListWithoutRenderItemProps<ItemT>
267
267
 
268
268
  /**
269
269
  * The maximum number of items to render in each incremental render batch. The more rendered at
270
- * once, the better the fill rate, but responsiveness my suffer because rendering content may
270
+ * once, the better the fill rate, but responsiveness may suffer because rendering content may
271
271
  * interfere with responding to button taps or other interactions.
272
272
  */
273
273
  maxToRenderPerBatch?: number | undefined;