@legendapp/list
Version:
Legend List is a drop-in replacement for FlatList with much better performance and supporting dynamically sized items.
664 lines (657 loc) • 25 kB
TypeScript
import * as React from 'react';
import { Key } from 'react';
import { ScrollViewProps, NativeSyntheticEvent as NativeSyntheticEvent$1, NativeScrollEvent as NativeScrollEvent$1, ScrollView, StyleProp as StyleProp$1, ViewStyle as ViewStyle$1, ScrollViewComponent, ScrollResponderMixin, Insets as Insets$1, Animated } from 'react-native';
interface MaintainVisibleContentPositionNormalized<ItemT = any> {
data: boolean;
size: boolean;
shouldRestorePosition?: (item: ItemT, index: number, data: readonly ItemT[]) => boolean;
}
type ListenerType = "activeStickyIndex" | "anchoredEndSpaceSize" | "debugComputedScroll" | "debugRawScroll" | "extraData" | "footerSize" | "headerSize" | "lastItemKeys" | "lastPositionUpdate" | "maintainVisibleContentPosition" | "numColumns" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "scrollAdjust" | "scrollAdjustPending" | "scrollAdjustUserOffset" | "scrollSize" | "snapToOffsets" | "stylePaddingTop" | "totalSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | `containerColumn${number}` | `containerSpan${number}` | `containerItemData${number}` | `containerItemKey${number}` | `containerPosition${number}` | `containerSticky${number}`;
type LegendListListenerType = Extract<ListenerType, "activeStickyIndex" | "anchoredEndSpaceSize" | "footerSize" | "headerSize" | "isAtEnd" | "isAtStart" | "isNearEnd" | "isNearStart" | "isWithinMaintainScrollAtEndThreshold" | "lastItemKeys" | "lastPositionUpdate" | "numContainers" | "numContainersPooled" | "otherAxisSize" | "readyToRender" | "snapToOffsets" | "totalSize">;
type ListenerTypeValueMap = {
activeStickyIndex: number;
anchoredEndSpaceSize: number;
animatedScrollY: any;
debugComputedScroll: number;
debugRawScroll: number;
extraData: any;
footerSize: number;
headerSize: number;
isAtEnd: boolean;
isAtStart: boolean;
isNearEnd: boolean;
isNearStart: boolean;
isWithinMaintainScrollAtEndThreshold: boolean;
lastItemKeys: string[];
lastPositionUpdate: number;
maintainVisibleContentPosition: MaintainVisibleContentPositionNormalized;
numColumns: number;
numContainers: number;
numContainersPooled: number;
otherAxisSize: number;
readyToRender: boolean;
scrollAdjust: number;
scrollAdjustPending: number;
scrollAdjustUserOffset: number;
scrollSize: {
width: number;
height: number;
};
snapToOffsets: number[];
stylePaddingTop: number;
totalSize: number;
} & {
[K in ListenerType as K extends `containerItemKey${number}` ? K : never]: string;
} & {
[K in ListenerType as K extends `containerItemData${number}` ? K : never]: any;
} & {
[K in ListenerType as K extends `containerPosition${number}` ? K : never]: number;
} & {
[K in ListenerType as K extends `containerColumn${number}` ? K : never]: number;
} & {
[K in ListenerType as K extends `containerSpan${number}` ? K : never]: number;
} & {
[K in ListenerType as K extends `containerSticky${number}` ? K : never]: boolean;
};
interface Insets {
top: number;
left: number;
bottom: number;
right: number;
}
interface NativeScrollEvent {
contentOffset: {
x: number;
y: number;
};
contentSize: {
width: number;
height: number;
};
layoutMeasurement: {
width: number;
height: number;
};
contentInset: Insets;
zoomScale: number;
}
interface NativeSyntheticEvent<T> {
nativeEvent: T;
}
type ViewStyle = Record<string, unknown>;
type StyleProp<T> = T | T[] | null | undefined | false;
type BaseScrollViewProps<TScrollView> = Omit<TScrollView, "contentOffset" | "maintainVisibleContentPosition" | "stickyHeaderIndices" | "removeClippedSubviews" | "children" | "onScroll">;
interface DataModeProps<ItemT, TItemType extends string | undefined> {
/**
* Array of items to render in the list.
* @required when using data mode
*/
data: ReadonlyArray<ItemT>;
/**
* Callback to render each item in the list.
* To use hooks in an item component, return that component from this callback.
* @required when using data mode
*/
renderItem: (props: LegendListRenderItemProps<ItemT, TItemType>) => React.ReactNode;
children?: never;
}
interface ChildrenModeProps {
/**
* React children elements to render as list items.
* Each child will be treated as an individual list item.
* @required when using children mode
*/
children: React.ReactNode;
data?: never;
renderItem?: never;
}
interface LegendListSpecificProps<ItemT, TItemType extends string | undefined> {
/**
* If true, aligns items at the end of the list.
* @default false
*/
alignItemsAtEnd?: boolean;
/**
* Keeps selected items mounted even when they scroll out of view.
* @default undefined
*/
alwaysRender?: AlwaysRenderConfig;
/**
* Style applied to each column's wrapper view.
*/
columnWrapperStyle?: ColumnWrapperStyle;
/**
* Version token that forces the list to treat data as updated even when the array reference is stable.
* Increment or change this when mutating the data array in place.
*/
dataVersion?: Key;
/**
* Distance in pixels to pre-render items ahead of the visible area.
* @default 250
*/
drawDistance?: number;
/**
* Estimated size of each item in pixels, a hint for the first render. After some
* items are rendered, the average size of rendered items will be used instead.
* @default undefined
*/
estimatedItemSize?: number;
/**
* Estimated size of the ScrollView in pixels, a hint for the first render to improve performance
* @default undefined
*/
estimatedListSize?: {
height: number;
width: number;
};
/**
* Extra data to trigger re-rendering when changed.
*/
extraData?: any;
/**
* In case items always have a fixed size, you can provide a function to return it.
*/
getFixedItemSize?: (item: ItemT, index: number, type: TItemType) => number | undefined;
/**
* Returns a stable item type used for pooling and size estimation.
*/
getItemType?: (item: ItemT, index: number) => TItemType;
/**
* Component to render between items, receiving the leading item as prop.
*/
ItemSeparatorComponent?: React.ComponentType<{
leadingItem: ItemT;
}>;
/**
* When true, the list initializes scrolled to the last item.
* Overrides `initialScrollIndex` and `initialScrollOffset` when data is available.
* @default false
*/
initialScrollAtEnd?: boolean;
/**
* Index to scroll to initially.
* @default 0
*/
initialScrollIndex?: number | {
index: number;
viewOffset?: number | undefined;
viewPosition?: number | undefined;
};
/**
* Initial scroll position in pixels.
* @default 0
*/
initialScrollOffset?: number;
/**
* Custom equality function to detect semantically unchanged items.
*/
itemsAreEqual?: (itemPrevious: ItemT, item: ItemT, index: number, data: readonly ItemT[]) => boolean;
/**
* Function to extract a unique key for each item.
*/
keyExtractor?: (item: ItemT, index: number) => string;
/**
* Component or element to render when the list is empty.
*/
ListEmptyComponent?: React.ComponentType<any> | React.ReactElement | null | undefined;
/**
* Component or element to render below the list.
*/
ListFooterComponent?: React.ComponentType<any> | React.ReactElement | null | undefined;
/**
* Style for the footer component.
*/
ListFooterComponentStyle?: StyleProp<ViewStyle> | undefined;
/**
* Component or element to render above the list.
*/
ListHeaderComponent?: React.ComponentType<any> | React.ReactElement | null | undefined;
/**
* Style for the header component.
*/
ListHeaderComponentStyle?: StyleProp<ViewStyle> | undefined;
/**
* Estimated height of the ListHeaderComponent. Provide this when the expected header height
* is known before layout so that only the items actually visible below the header are rendered
* on the initial frame, rather than a full screen's worth of items that are hidden behind it.
* The measured header size still replaces this value after layout.
*/
estimatedHeaderSize?: number;
/**
* If true, auto-scrolls to end when new items are added.
* Use an options object to opt into specific triggers and control whether that scroll is animated.
* @default false
*/
maintainScrollAtEnd?: boolean | MaintainScrollAtEndOptions;
/**
* Distance threshold in percentage of screen size to trigger maintainScrollAtEnd.
* @default 0.1
*/
maintainScrollAtEndThreshold?: number;
/**
* Maintains visibility of content.
* - scroll (default: true) stabilizes during size/layout changes while scrolling.
* - data (default: false) stabilizes when the data array changes; passing true also sets the RN maintainVisibleContentPosition prop.
* - shouldRestorePosition can opt out specific items from data-change anchoring.
* - undefined (default) enables scroll stabilization but skips data-change anchoring.
* - true enables both behaviors; false disables both.
*/
maintainVisibleContentPosition?: boolean | MaintainVisibleContentPositionConfig<ItemT>;
/**
* Keeps an item visually anchored to the start by adding trailing space when the content below it underflows.
*/
anchoredEndSpace?: AnchoredEndSpaceConfig;
/**
* Adjusts the effective end content inset for web lists without replacing the base contentInset.
* The adjustment is also rendered as real content padding so the browser scroll range includes it.
*/
contentInsetEndAdjustment?: number;
/**
* Number of columns to render items in.
* @default 1
*/
numColumns?: number;
/**
* Force RTL mode for this list instance.
* When undefined, uses React Native's global I18nManager.isRTL.
* @default undefined
*/
rtl?: boolean;
/**
* Called when scrolling reaches the end within onEndReachedThreshold.
*/
onEndReached?: ((info: {
distanceFromEnd: number;
}) => void) | null | undefined;
/**
* How close to the end (in fractional units of visible length) to trigger onEndReached.
* @default 0.5
*/
onEndReachedThreshold?: number | null | undefined;
/**
* Called when an item's size changes.
*/
onItemSizeChanged?: (info: {
size: number;
previous: number;
index: number;
itemKey: string;
itemData: ItemT;
}) => void;
/**
* Called after the initial render work completes.
*/
onLoad?: (info: {
elapsedTimeInMs: number;
}) => void;
/**
* Called when list layout metrics change.
*/
onMetricsChange?: (metrics: LegendListMetrics) => void;
/**
* Function to call when the user pulls to refresh.
*/
onRefresh?: () => void;
/**
* Called when the list scrolls.
*/
onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
/**
* Called when scrolling reaches the start within onStartReachedThreshold.
*/
onStartReached?: ((info: {
distanceFromStart: number;
}) => void) | null | undefined;
/**
* How close to the start (in fractional units of visible length) to trigger onStartReached.
* @default 0.5
*/
onStartReachedThreshold?: number | null | undefined;
/**
* Called when the sticky header changes.
*/
onStickyHeaderChange?: (info: {
index: number;
item: any;
}) => void;
/**
* Called when the viewability of items changes.
*/
onViewableItemsChanged?: OnViewableItemsChanged<ItemT> | undefined;
/**
* Customize layout for multi-column lists, such as allowing items to span multiple columns.
*/
overrideItemLayout?: (layout: {
span?: number;
}, item: ItemT, index: number, maxColumns: number, extraData?: any) => void;
/**
* Offset in pixels for the refresh indicator.
* @default 0
*/
progressViewOffset?: number;
/**
* If true, recycles item views for better performance.
* @default false
*/
recycleItems?: boolean;
/**
* Ref to the underlying ScrollView component.
*/
refScrollView?: React.Ref<any>;
/**
* If true, shows a refresh indicator.
* @default false
*/
refreshing?: boolean;
/**
* Render custom ScrollView component.
* Note: When using `stickyHeaderIndices`, you must provide an Animated ScrollView component.
* @default (props) => <ScrollView {...props} />
*/
renderScrollComponent?: (props: any) => React.ReactElement | null;
/**
* Array of item indices to use as snap points.
*/
snapToIndices?: number[];
/**
* Configuration for determining item viewability.
*/
viewabilityConfig?: ViewabilityConfig;
/**
* Pairs of viewability configs and their callbacks for tracking visibility.
*/
viewabilityConfigCallbackPairs?: ViewabilityConfigCallbackPairs<ItemT> | undefined;
/**
* Array of child indices determining which children get docked to the top of the screen when scrolling.
* For example, passing stickyHeaderIndices={[0]} will cause the first child to be fixed to the top of the scroll view.
* Not supported in conjunction with horizontal={true}.
* @default undefined
*/
stickyHeaderIndices?: number[];
/**
* Configuration for sticky headers.
* @default undefined
*/
stickyHeaderConfig?: StickyHeaderConfig;
/**
* Web only: when true, listens to window/body scrolling instead of rendering a scrollable list container.
* @default false
*/
useWindowScroll?: boolean;
}
type LegendListPropsBase<ItemT, TScrollViewProps = Record<string, any>, TItemType extends string | undefined = string | undefined> = BaseScrollViewProps<TScrollViewProps> & LegendListSpecificProps<ItemT, TItemType> & (DataModeProps<ItemT, TItemType> | ChildrenModeProps);
interface MaintainVisibleContentPositionConfig<ItemT = any> {
data?: boolean;
size?: boolean;
shouldRestorePosition?: (item: ItemT, index: number, data: readonly ItemT[]) => boolean;
}
interface AnchoredEndSpaceConfig {
anchorIndex: number;
anchorOffset?: number;
anchorMaxSize?: number;
includeInEndInset?: boolean;
onSizeChanged?: (size: number) => void;
}
interface StickyHeaderConfig {
/**
* Specifies how far from the top edge sticky headers should start sticking.
* Useful for scenarios with a fixed navbar or header, where sticky elements pin below it..
* @default 0
*/
offset?: number;
/**
* Component to render as a backdrop behind the sticky header.
* @default undefined
*/
backdropComponent?: React.ComponentType<any> | React.ReactElement | null | undefined;
}
interface AlwaysRenderConfig {
top?: number;
bottom?: number;
indices?: number[];
keys?: string[];
}
interface MaintainScrollAtEndOnOptions {
dataChange?: boolean;
itemLayout?: boolean;
layout?: boolean;
}
interface MaintainScrollAtEndOptions {
/**
* Whether maintainScrollAtEnd should animate when it scrolls to the end.
*/
animated?: boolean;
/**
* Which events should keep the list pinned to the end.
* - If omitted, object values default to all triggers.
* - If provided, only the keys set to `true` are enabled.
*/
on?: MaintainScrollAtEndOnOptions;
}
interface ColumnWrapperStyle {
rowGap?: number;
gap?: number;
columnGap?: number;
}
interface LegendListMetrics {
headerSize: number;
footerSize: number;
}
interface LegendListAverageItemSize {
average: number;
count: number;
}
interface LegendListRenderItemProps<ItemT, TItemType extends string | number | undefined = string | number | undefined> {
data: readonly ItemT[];
extraData: any;
index: number;
item: ItemT;
type: TItemType;
}
type LegendListState = {
activeStickyIndex: number;
contentLength: number;
data: readonly any[];
elementAtIndex: (index: number) => any;
end: number;
endBuffered: number;
isAtEnd: boolean;
isAtStart: boolean;
isNearEnd: boolean;
isNearStart: boolean;
isEndReached: boolean;
isStartReached: boolean;
isWithinMaintainScrollAtEndThreshold: boolean;
getAverageItemSizes: () => Record<string, LegendListAverageItemSize>;
listen: <T extends LegendListListenerType>(listenerType: T, callback: (value: ListenerTypeValueMap[T]) => void) => () => void;
listenToPosition: (key: string, callback: (value: number) => void) => () => void;
positionAtIndex: (index: number) => number;
positionByKey: (key: string) => number | undefined;
scroll: number;
scrollLength: number;
scrollVelocity: number;
sizeAtIndex: (index: number) => number;
sizes: Map<string, number>;
start: number;
startBuffered: number;
};
type LegendListRef$1 = {
/**
* Displays the scroll indicators momentarily.
*/
flashScrollIndicators(): void;
/**
* Returns the native ScrollView component reference.
*/
getNativeScrollRef(): any;
/**
* Returns the scroll responder instance for handling scroll events.
*/
getScrollableNode(): any;
/**
* Returns the ScrollResponderMixin for advanced scroll handling.
*/
getScrollResponder(): any;
/**
* Returns the internal state of the scroll virtualization.
*/
getState(): LegendListState;
/**
* Scrolls a specific index into view.
* @param params - Parameters for scrolling.
* @param params.animated - If true, animates the scroll. Default: true.
* @param params.index - The index to scroll to.
*/
scrollIndexIntoView(params: {
animated?: boolean | undefined;
index: number;
}): Promise<void>;
/**
* Scrolls a specific index into view.
* @param params - Parameters for scrolling.
* @param params.animated - If true, animates the scroll. Default: true.
* @param params.item - The item to scroll to.
*/
scrollItemIntoView(params: {
animated?: boolean | undefined;
item: any;
}): Promise<void>;
/**
* Scrolls to the end of the list.
* @param options - Options for scrolling.
* @param options.animated - If true, animates the scroll. Default: true.
* @param options.viewOffset - Offset from the target position.
*/
scrollToEnd(options?: {
animated?: boolean | undefined;
viewOffset?: number | undefined;
}): Promise<void>;
/**
* Scrolls to a specific index in the list.
* @param params - Parameters for scrolling.
* @param params.animated - If true, animates the scroll. Default: true.
* @param params.index - The index to scroll to.
* @param params.viewOffset - Offset from the target position.
* @param params.viewPosition - Position of the item in the viewport (0 to 1).
*/
scrollToIndex(params: {
animated?: boolean | undefined;
index: number;
viewOffset?: number | undefined;
viewPosition?: number | undefined;
}): Promise<void>;
/**
* Scrolls to a specific item in the list.
* @param params - Parameters for scrolling.
* @param params.animated - If true, animates the scroll. Default: true.
* @param params.item - The item to scroll to.
* @param params.viewOffset - Offset from the target position.
* @param params.viewPosition - Position of the item in the viewport (0 to 1).
*/
scrollToItem(params: {
animated?: boolean | undefined;
item: any;
viewOffset?: number | undefined;
viewPosition?: number | undefined;
}): Promise<void>;
/**
* Scrolls to a specific offset in pixels.
* @param params - Parameters for scrolling.
* @param params.offset - The pixel offset to scroll to.
* @param params.animated - If true, animates the scroll. Default: true.
*/
scrollToOffset(params: {
offset: number;
animated?: boolean | undefined;
}): Promise<void>;
/**
* Sets or adds to the offset of the visible content anchor.
* @param value - The offset to set or add.
* @param animated - If true, uses Animated to animate the change.
*/
setVisibleContentAnchorOffset(value: number | ((val: number) => number)): void;
/**
* Sets whether scroll processing is enabled.
* @param enabled - If true, scroll processing is enabled.
*/
setScrollProcessingEnabled(enabled: boolean): void;
/**
* Clears internal virtualization caches.
* @param options - Cache clearing options.
* @param options.mode - `sizes` clears measurement caches. `full` also clears key/position caches.
*/
clearCaches(options?: {
mode?: "sizes" | "full";
}): void;
/**
* Reports an externally measured content inset. Pass null/undefined to clear.
* Values are merged on top of props/animated/native insets.
*/
reportContentInset(inset?: Partial<Insets> | null): void;
};
interface ViewToken<ItemT = any> {
containerId: number;
index: number;
isViewable: boolean;
item: ItemT;
key: string;
}
interface ViewabilityConfigCallbackPair<ItemT = any> {
onViewableItemsChanged?: OnViewableItemsChanged<ItemT>;
viewabilityConfig: ViewabilityConfig;
}
type ViewabilityConfigCallbackPairs<ItemT> = ViewabilityConfigCallbackPair<ItemT>[];
interface OnViewableItemsChangedInfo<ItemT> {
changed: Array<ViewToken<ItemT>>;
end: number;
endBuffered: number;
start: number;
startBuffered: number;
viewableItems: Array<ViewToken<ItemT>>;
}
type OnViewableItemsChanged<ItemT> = ((info: OnViewableItemsChangedInfo<ItemT>) => void) | null;
interface ViewabilityConfig {
/**
* A unique ID to identify this viewability config
*/
id?: string;
/**
* Minimum amount of time (in milliseconds) that an item must be physically viewable before the
* viewability callback will be fired. A high number means that scrolling through content without
* stopping will not mark the content as viewable.
*/
minimumViewTime?: number | undefined;
/**
* Percent of viewport that must be covered for a partially occluded item to count as
* "viewable", 0-100. Fully visible items are always considered viewable. A value of 0 means
* that a single pixel in the viewport makes the item viewable, and a value of 100 means that
* an item must be either entirely visible or cover the entire viewport to count as viewable.
*/
viewAreaCoveragePercentThreshold?: number | undefined;
/**
* Similar to `viewAreaCoveragePercentThreshold`, but considers the percent of the item that is visible,
* rather than the fraction of the viewable area it covers.
*/
itemVisiblePercentThreshold?: number | undefined;
/**
* Nothing is considered viewable until the user scrolls or `recordInteraction` is called after
* render.
*/
waitForInteraction?: boolean | undefined;
}
type LegendListPropsOverrides<ItemT, TItemType extends string | undefined> = Omit<LegendListPropsBase<ItemT, ScrollViewProps, TItemType>, "anchoredEndSpace" | "contentInsetEndAdjustment" | "onScroll" | "refScrollView" | "renderScrollComponent" | "ListHeaderComponentStyle" | "ListFooterComponentStyle"> & {
onScroll?: (event: NativeSyntheticEvent$1<NativeScrollEvent$1>) => void;
refScrollView?: React.Ref<ScrollView>;
renderScrollComponent?: (props: ScrollViewProps) => React.ReactElement<ScrollViewProps>;
ListHeaderComponentStyle?: StyleProp$1<ViewStyle$1> | undefined;
ListFooterComponentStyle?: StyleProp$1<ViewStyle$1> | undefined;
};
type LegendListProps<ItemT = any, TItemType extends string | undefined = string | undefined> = LegendListPropsOverrides<ItemT, TItemType>;
type LegendListRef = Omit<LegendListRef$1, "getNativeScrollRef" | "getScrollResponder" | "reportContentInset"> & {
getNativeScrollRef(): React.ElementRef<typeof ScrollViewComponent>;
getScrollResponder(): ScrollResponderMixin;
reportContentInset(inset?: Partial<Insets$1> | null): void;
};
type LegendListComponent = <ItemT = any>(props: LegendListProps<ItemT> & React.RefAttributes<LegendListRef>) => React.ReactElement | null;
declare const AnimatedLegendList: Animated.AnimatedComponent<LegendListComponent>;
export { AnimatedLegendList };