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 * @format
8 */
9
10import type * as React from 'react';
11import type {LayoutChangeEvent} from '../../types';
12import {StyleProp} from '../StyleSheet/StyleSheet';
13import {ViewStyle} from '../StyleSheet/StyleSheetTypes';
14import type {
15  ScrollResponderMixin,
16  ScrollView,
17  ScrollViewProps,
18} from '../Components/ScrollView/ScrollView';
19import type {View} from '../Components/View/View';
20
21export interface ViewToken {
22  item: any;
23  key: string;
24  index: number | null;
25  isViewable: boolean;
26  section?: any;
27}
28
29export interface ViewabilityConfig {
30  /**
31   * Minimum amount of time (in milliseconds) that an item must be physically viewable before the
32   * viewability callback will be fired. A high number means that scrolling through content without
33   * stopping will not mark the content as viewable.
34   */
35  minimumViewTime?: number | undefined;
36
37  /**
38   * Percent of viewport that must be covered for a partially occluded item to count as
39   * "viewable", 0-100. Fully visible items are always considered viewable. A value of 0 means
40   * that a single pixel in the viewport makes the item viewable, and a value of 100 means that
41   * an item must be either entirely visible or cover the entire viewport to count as viewable.
42   */
43  viewAreaCoveragePercentThreshold?: number | undefined;
44
45  /**
46   * Similar to `viewAreaCoveragePercentThreshold`, but considers the percent of the item that is visible,
47   * rather than the fraction of the viewable area it covers.
48   */
49  itemVisiblePercentThreshold?: number | undefined;
50
51  /**
52   * Nothing is considered viewable until the user scrolls or `recordInteraction` is called after
53   * render.
54   */
55  waitForInteraction?: boolean | undefined;
56}
57
58export interface ViewabilityConfigCallbackPair {
59  viewabilityConfig: ViewabilityConfig;
60  onViewableItemsChanged:
61    | ((info: {
62        viewableItems: Array<ViewToken>;
63        changed: Array<ViewToken>;
64      }) => void)
65    | null;
66}
67
68export type ViewabilityConfigCallbackPairs = ViewabilityConfigCallbackPair[];
69
70/**
71 * @see https://reactnative.dev/docs/flatlist#props
72 */
73
74export interface ListRenderItemInfo<ItemT> {
75  item: ItemT;
76
77  index: number;
78
79  separators: {
80    highlight: () => void;
81    unhighlight: () => void;
82    updateProps: (select: 'leading' | 'trailing', newProps: any) => void;
83  };
84}
85
86export type ListRenderItem<ItemT> = (
87  info: ListRenderItemInfo<ItemT>,
88) => React.ReactElement | null;
89
90/**
91 * @see https://reactnative.dev/docs/virtualizedlist
92 */
93export class VirtualizedList<ItemT> extends React.Component<
94  VirtualizedListProps<ItemT>
95> {
96  scrollToEnd: (params?: {animated?: boolean | undefined}) => void;
97  scrollToIndex: (params: {
98    animated?: boolean | undefined;
99    index: number;
100    viewOffset?: number | undefined;
101    viewPosition?: number | undefined;
102  }) => void;
103  scrollToItem: (params: {
104    animated?: boolean | undefined;
105    item: ItemT;
106    viewOffset?: number | undefined;
107    viewPosition?: number | undefined;
108  }) => void;
109
110  /**
111   * Scroll to a specific content pixel offset in the list.
112   * Param `offset` expects the offset to scroll to. In case of horizontal is true, the
113   * offset is the x-value, in any other case the offset is the y-value.
114   * Param `animated` (true by default) defines whether the list should do an animation while scrolling.
115   */
116  scrollToOffset: (params: {
117    animated?: boolean | undefined;
118    offset: number;
119  }) => void;
120
121  recordInteraction: () => void;
122
123  getScrollRef: () =>
124    | React.ElementRef<typeof ScrollView>
125    | React.ElementRef<typeof View>
126    | null;
127
128  getScrollResponder: () => ScrollResponderMixin | null;
129}
130
131/**
132 * @see https://reactnative.dev/docs/virtualizedlist#props
133 */
134
135export interface VirtualizedListProps<ItemT>
136  extends VirtualizedListWithoutRenderItemProps<ItemT> {
137  renderItem: ListRenderItem<ItemT> | null | undefined;
138}
139
140export interface VirtualizedListWithoutRenderItemProps<ItemT>
141  extends ScrollViewProps {
142  /**
143   * Rendered in between each item, but not at the top or bottom
144   */
145  ItemSeparatorComponent?: React.ComponentType<any> | null | undefined;
146
147  /**
148   * Rendered when the list is empty. Can be a React Component Class, a render function, or
149   * a rendered element.
150   */
151  ListEmptyComponent?:
152    | React.ComponentType<any>
153    | React.ReactElement
154    | null
155    | undefined;
156
157  /**
158   * Rendered at the bottom of all the items. Can be a React Component Class, a render function, or
159   * a rendered element.
160   */
161  ListFooterComponent?:
162    | React.ComponentType<any>
163    | React.ReactElement
164    | null
165    | undefined;
166
167  /**
168   * Styling for internal View for ListFooterComponent
169   */
170  ListFooterComponentStyle?: StyleProp<ViewStyle> | undefined;
171
172  /**
173   * Rendered at the top of all the items. Can be a React Component Class, a render function, or
174   * a rendered element.
175   */
176  ListHeaderComponent?:
177    | React.ComponentType<any>
178    | React.ReactElement
179    | null
180    | undefined;
181
182  /**
183   * Styling for internal View for ListHeaderComponent
184   */
185  ListHeaderComponentStyle?: StyleProp<ViewStyle> | undefined;
186
187  /**
188   * The default accessor functions assume this is an Array<{key: string}> but you can override
189   * getItem, getItemCount, and keyExtractor to handle any type of index-based data.
190   */
191  data?: any;
192
193  /**
194   * `debug` will turn on extra logging and visual overlays to aid with debugging both usage and
195   * implementation, but with a significant perf hit.
196   */
197  debug?: boolean | undefined;
198
199  /**
200   * DEPRECATED: Virtualization provides significant performance and memory optimizations, but fully
201   * unmounts react instances that are outside of the render window. You should only need to disable
202   * this for debugging purposes.
203   */
204  disableVirtualization?: boolean | undefined;
205
206  /**
207   * A marker property for telling the list to re-render (since it implements `PureComponent`). If
208   * any of your `renderItem`, Header, Footer, etc. functions depend on anything outside of the
209   * `data` prop, stick it here and treat it immutably.
210   */
211  extraData?: any;
212
213  /**
214   * A generic accessor for extracting an item from any sort of data blob.
215   */
216  getItem?: ((data: any, index: number) => ItemT) | undefined;
217
218  /**
219   * Determines how many items are in the data blob.
220   */
221  getItemCount?: ((data: any) => number) | undefined;
222
223  getItemLayout?:
224    | ((
225        data: any,
226        index: number,
227      ) => {
228        length: number;
229        offset: number;
230        index: number;
231      })
232    | undefined;
233
234  horizontal?: boolean | null | undefined;
235
236  /**
237   * How many items to render in the initial batch. This should be enough to fill the screen but not
238   * much more. Note these items will never be unmounted as part of the windowed rendering in order
239   * to improve perceived performance of scroll-to-top actions.
240   */
241  initialNumToRender?: number | undefined;
242
243  /**
244   * Instead of starting at the top with the first item, start at `initialScrollIndex`. This
245   * disables the "scroll to top" optimization that keeps the first `initialNumToRender` items
246   * always rendered and immediately renders the items starting at this initial index. Requires
247   * `getItemLayout` to be implemented.
248   */
249  initialScrollIndex?: number | null | undefined;
250
251  /**
252   * Reverses the direction of scroll. Uses scale transforms of -1.
253   */
254  inverted?: boolean | null | undefined;
255
256  keyExtractor?: ((item: ItemT, index: number) => string) | undefined;
257
258  /**
259   * The maximum number of items to render in each incremental render batch. The more rendered at
260   * once, the better the fill rate, but responsiveness my suffer because rendering content may
261   * interfere with responding to button taps or other interactions.
262   */
263  maxToRenderPerBatch?: number | undefined;
264
265  onEndReached?: ((info: {distanceFromEnd: number}) => void) | null | undefined;
266
267  onEndReachedThreshold?: number | null | undefined;
268
269  onLayout?: ((event: LayoutChangeEvent) => void) | undefined;
270
271  /**
272   * If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make
273   * sure to also set the `refreshing` prop correctly.
274   */
275  onRefresh?: (() => void) | null | undefined;
276
277  /**
278   * Used to handle failures when scrolling to an index that has not been measured yet.
279   * Recommended action is to either compute your own offset and `scrollTo` it, or scroll as far
280   * as possible and then try again after more items have been rendered.
281   */
282  onScrollToIndexFailed?:
283    | ((info: {
284        index: number;
285        highestMeasuredFrameIndex: number;
286        averageItemLength: number;
287      }) => void)
288    | undefined;
289
290  /**
291   * Called when the viewability of rows changes, as defined by the
292   * `viewabilityConfig` prop.
293   */
294  onViewableItemsChanged?:
295    | ((info: {
296        viewableItems: Array<ViewToken>;
297        changed: Array<ViewToken>;
298      }) => void)
299    | null
300    | undefined;
301
302  /**
303   * Set this when offset is needed for the loading indicator to show correctly.
304   * @platform android
305   */
306  progressViewOffset?: number | undefined;
307
308  /**
309   * Set this true while waiting for new data from a refresh.
310   */
311  refreshing?: boolean | null | undefined;
312
313  /**
314   * Note: may have bugs (missing content) in some circumstances - use at your own risk.
315   *
316   * This may improve scroll performance for large lists.
317   */
318  removeClippedSubviews?: boolean | undefined;
319
320  /**
321   * Render a custom scroll component, e.g. with a differently styled `RefreshControl`.
322   */
323  renderScrollComponent?:
324    | ((props: ScrollViewProps) => React.ReactElement<ScrollViewProps>)
325    | undefined;
326
327  /**
328   * Amount of time between low-pri item render batches, e.g. for rendering items quite a ways off
329   * screen. Similar fill rate/responsiveness tradeoff as `maxToRenderPerBatch`.
330   */
331  updateCellsBatchingPeriod?: number | undefined;
332
333  viewabilityConfig?: ViewabilityConfig | undefined;
334
335  viewabilityConfigCallbackPairs?: ViewabilityConfigCallbackPairs | undefined;
336
337  /**
338   * Determines the maximum number of items rendered outside of the visible area, in units of
339   * visible lengths. So if your list fills the screen, then `windowSize={21}` (the default) will
340   * render the visible screen area plus up to 10 screens above and 10 below the viewport. Reducing
341   * this number will reduce memory consumption and may improve performance, but will increase the
342   * chance that fast scrolling may reveal momentary blank areas of unrendered content.
343   */
344  windowSize?: number | undefined;
345
346  CellRendererComponent?: React.ComponentType<any> | undefined;
347}
348