UNPKG

@legendapp/list

Version:

Legend List is a drop-in replacement for FlatList with much better performance and supporting dynamically sized items.

284 lines (279 loc) 8.87 kB
import * as React from 'react'; import { Platform } from 'react-native'; import { internal, LegendList } from '@legendapp/list/react-native'; // src/section-list/SectionList.tsx // src/section-list/flattenSections.ts var defaultKeyExtractor = (item, index) => { var _a; const key = (_a = item == null ? void 0 : item.key) != null ? _a : item == null ? void 0 : item.id; return key != null ? String(key) : String(index); }; var getSectionKey = (section, sectionIndex) => { var _a; return (_a = section.key) != null ? _a : `section-${sectionIndex}`; }; function buildSectionListData({ sections, renderSectionHeader, renderSectionFooter, ItemSeparatorComponent, SectionSeparatorComponent, stickySectionHeadersEnabled, keyExtractor = defaultKeyExtractor }) { var _a, _b; const data = []; const sectionMeta = []; const stickyHeaderIndices = []; let absoluteItemIndex = 0; for (let sectionIndex = 0; sectionIndex < sections.length; sectionIndex++) { const section = sections[sectionIndex]; const items = (_a = section.data) != null ? _a : []; const meta = { items: [] }; const sectionKey = getSectionKey(section, sectionIndex); const hasHeader = typeof renderSectionHeader === "function"; const hasFooter = typeof renderSectionFooter === "function"; const hasItemSeparator = Boolean(ItemSeparatorComponent || section.ItemSeparatorComponent); const hasSectionSeparator = Boolean(SectionSeparatorComponent); if (hasHeader) { const headerIndex = data.length; data.push({ key: `${sectionKey}:header`, kind: "header", section, sectionIndex }); meta.header = headerIndex; if (stickySectionHeadersEnabled) { stickyHeaderIndices.push(headerIndex); } } for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { const item = items[itemIndex]; const itemKeyExtractor = (_b = section.keyExtractor) != null ? _b : keyExtractor; const itemKey = itemKeyExtractor(item, itemIndex); data.push({ absoluteItemIndex: absoluteItemIndex++, item, itemIndex, key: `${sectionKey}:item:${itemKey}`, kind: "item", section, sectionIndex }); meta.items.push(data.length - 1); if (hasItemSeparator && itemIndex < items.length - 1) { data.push({ key: `${sectionKey}:separator:${itemIndex}`, kind: "item-separator", leadingItem: item, leadingItemIndex: itemIndex, section, sectionIndex, trailingItem: items[itemIndex + 1] }); } } if (hasFooter) { data.push({ key: `${sectionKey}:footer`, kind: "footer", section, sectionIndex }); meta.footer = data.length - 1; } const isLastSection = sectionIndex === sections.length - 1; if (hasSectionSeparator && !isLastSection) { data.push({ key: `${sectionKey}:section-separator`, kind: "section-separator", leadingSection: section, leadingSectionIndex: sectionIndex, trailingSection: sections[sectionIndex + 1] }); } sectionMeta.push(meta); } return { data, sectionMeta, stickyHeaderIndices }; } // src/section-list/SectionList.tsx var { typedForwardRef, typedMemo } = internal; var defaultSeparators = { highlight: () => { }, unhighlight: () => { }, updateProps: () => { } }; function resolveSeparatorComponent(component, props) { if (!component) return null; if (React.isValidElement(component)) { return component; } const Component = component; return /* @__PURE__ */ React.createElement(Component, { ...props }); } var SectionList = typedMemo( typedForwardRef(function SectionListInner(props, ref) { const { sections, renderItem: renderItemProp, renderSectionHeader, renderSectionFooter, ItemSeparatorComponent, SectionSeparatorComponent, stickySectionHeadersEnabled = Platform.OS === "ios", keyExtractor, extraData, onViewableItemsChanged, horizontal, ...restProps } = props; const legendListRef = React.useRef(null); const flattened = React.useMemo( () => buildSectionListData({ ItemSeparatorComponent, keyExtractor, renderSectionFooter, renderSectionHeader, SectionSeparatorComponent, sections, stickySectionHeadersEnabled: !horizontal && stickySectionHeadersEnabled }), [ sections, extraData, renderSectionHeader, renderSectionFooter, ItemSeparatorComponent, SectionSeparatorComponent, stickySectionHeadersEnabled, keyExtractor, horizontal ] ); const { data, sectionMeta, stickyHeaderIndices } = flattened; const handleViewableItemsChanged = React.useMemo(() => { if (!onViewableItemsChanged) return void 0; return ({ viewableItems, changed, start, end, startBuffered, endBuffered }) => { const mapToken = (token) => { if (token.item.kind !== "item") return null; return { index: token.item.itemIndex, isViewable: token.isViewable, item: token.item.item, key: token.key, section: token.item.section }; }; const mappedViewable = viewableItems.map(mapToken).filter(Boolean); const mappedChanged = changed.map(mapToken).filter(Boolean); onViewableItemsChanged({ changed: mappedChanged, end, endBuffered, start, startBuffered, viewableItems: mappedViewable }); }; }, [onViewableItemsChanged]); const renderItem = React.useCallback( ({ item }) => { var _a, _b; switch (item.kind) { case "header": return renderSectionHeader ? renderSectionHeader({ section: item.section }) : null; case "footer": return renderSectionFooter ? renderSectionFooter({ section: item.section }) : null; case "item": { const render = (_a = item.section.renderItem) != null ? _a : renderItemProp; if (!render) return null; return render({ index: item.itemIndex, item: item.item, section: item.section, separators: defaultSeparators }); } case "item-separator": { const SeparatorComponent = (_b = item.section.ItemSeparatorComponent) != null ? _b : ItemSeparatorComponent; return resolveSeparatorComponent(SeparatorComponent, { leadingItem: item.leadingItem, leadingSection: item.section, section: item.section, trailingItem: item.trailingItem, trailingSection: item.section }); } case "section-separator": return resolveSeparatorComponent(SectionSeparatorComponent, { leadingItem: void 0, leadingSection: item.leadingSection, section: item.leadingSection, trailingItem: void 0, trailingSection: item.trailingSection }); default: return null; } }, [ ItemSeparatorComponent, SectionSeparatorComponent, renderItemProp, renderSectionFooter, renderSectionHeader ] ); const scrollToLocation = React.useCallback( ({ sectionIndex, itemIndex, viewOffset, viewPosition, animated }) => { var _a, _b, _c; const meta = sectionMeta[sectionIndex]; if (!meta) return; const target = itemIndex === -1 ? (_b = (_a = meta.header) != null ? _a : meta.items[0]) != null ? _b : meta.footer : meta.items[itemIndex]; if (target === void 0) return; (_c = legendListRef.current) == null ? void 0 : _c.scrollToIndex({ animated, index: target, viewOffset, viewPosition }); }, [sectionMeta] ); React.useImperativeHandle( ref, () => ({ ...legendListRef.current, scrollToLocation }), [scrollToLocation] ); return /* @__PURE__ */ React.createElement( LegendList, { ...restProps, columnWrapperStyle: void 0, data, getItemType: (item) => item.kind, keyExtractor: (item) => item.key, numColumns: 1, onViewableItemsChanged: handleViewableItemsChanged, ref: legendListRef, renderItem, stickyHeaderIndices: !horizontal ? stickyHeaderIndices : void 0 } ); }) ); export { SectionList };