UNPKG

flash-section-list

Version:
290 lines (289 loc) 11.7 kB
"use strict"; import { FlashList } from '@shopify/flash-list'; import { useImperativeHandle, useMemo, useRef, useState } from 'react'; import { binarySearchClosestIndex, findFirstProp, lcm, omit } from "./utils.js"; import React from 'react'; import { View } from 'react-native'; import { useDummy } from "./useDummy.js"; import Dummy from "./Dummy.js"; import LayoutManager from "./LayoutManager.js"; import { jsx as _jsx } from "react/jsx-runtime"; const methodNames = ['prepareForLayoutAnimationRender', 'recordInteraction', 'recomputeViewableItems', 'scrollToEnd', 'scrollToIndex', 'scrollToItem', 'scrollToOffset']; const isElementSection = section => { return /*#__PURE__*/React.isValidElement(section.element); }; const convertDataSectionFrom = section => { return { data: [0], renderItem: () => { return section.element; }, stickyHeaderIndices: section.sticky ? [0] : undefined, type: section.type }; }; const flex1 = { flex: 1 }; const omitProps = ['data', 'renderItem', 'getItemType', 'getItemLayout', 'overrideItemLayout', 'stickyHeaderIndices', 'ListHeaderComponent', 'ListHeaderComponentStyle', 'ListFooterComponent', 'ListFooterComponentStyle', 'numOfColumns']; export function FlashSectionListBuilder() { const buildProps = { FlashListComponent: FlashList, DummyClass: Dummy, LayoutManagerClass: LayoutManager }; return { build: () => { const FlashListComponent = buildProps.FlashListComponent; const DummyClass = buildProps.DummyClass; const LayoutManagerClass = buildProps.LayoutManagerClass; function FlashSectionList(propsOrigin, ref) { const flashlist = useRef(null); const [containerWidth, setContainerWidth] = useState(undefined); let { sections, ...props } = propsOrigin; props = omit(propsOrigin, omitProps, false); const contentContainerPaddingHorizontal = useMemo(() => { const paddingHorizontal = findFirstProp(props.contentContainerStyle, ['paddingHorizontal', 'paddingLeft']); return typeof paddingHorizontal === 'number' ? paddingHorizontal * 2 : 0; }, [props.contentContainerStyle]); const Dummy = useDummy({ horizontal: props.horizontal, DummyClass, LayoutManagerClass }); const { dataSections, sectionStartIndices, serializedData, stickyHeaderIndices, numOfColumns } = useMemo(() => { const dataSections = []; const numOfColumnArray = []; const stickyHeaderIndices = []; let index = 0; const sectionStartIndices = []; const serializedData = sections.reduce((acc, cur) => { const section = isElementSection(cur) ? convertDataSectionFrom(cur) : cur; dataSections.push(section); const { data, header, footer, stickyHeaderIndices: stickyHeaderIndicesOfSection, numOfColumns = 1 } = section; let length = data.length; sectionStartIndices.push(index); numOfColumnArray.push(numOfColumns); if (header) { if (header.sticky) { stickyHeaderIndices.push(index); } length += 1; acc.push(header); } acc.push(...data); if (stickyHeaderIndicesOfSection) { stickyHeaderIndices.push(...stickyHeaderIndicesOfSection.map(indexWithinSection => indexWithinSection + index + (header ? 1 : 0))); } const dummyCount = data.length % numOfColumns !== 0 ? numOfColumns - data.length % numOfColumns : 0; length += dummyCount; section.dummyCount = dummyCount; for (let i = 0; i < dummyCount; i++) { acc.push(Dummy); } if (footer) { if (footer.sticky) { stickyHeaderIndices.push(index + length); } length += 1; acc.push(footer); } index += length; return acc; }, []); return { dataSections, sectionStartIndices, serializedData, stickyHeaderIndices, numOfColumns: lcm(numOfColumnArray) }; }, [Dummy, sections]); useImperativeHandle(ref, () => { const mothods = methodNames.reduce((acc, cur) => { acc[cur] = (...props) => { flashlist.current?.[cur](...props); }; return acc; }, {}); return { ...flashlist.current, ...mothods, scrollToSection: params => { if (!Array.isArray(sectionStartIndices) || sectionStartIndices.length === 0) { return; } const index = sectionStartIndices[params.sectionIndex]; if (index === undefined) { return; } flashlist.current?.scrollToIndex?.({ ...params, index }); } }; }, [sectionStartIndices]); return /*#__PURE__*/_jsx(FlashListComponent, { ...props, ref: flashlist, onLayout: !props.horizontal ? e => { setContainerWidth(e.nativeEvent?.layout?.width); props.onLayout?.(e); } : props.onLayout, data: serializedData, stickyHeaderIndices: stickyHeaderIndices, numColumns: numOfColumns, renderItem: ({ index, item, ...etc }) => { const sectionIndex = binarySearchClosestIndex(sectionStartIndices, index); const section = dataSections[sectionIndex]; const sectionStartIndex = sectionStartIndices[sectionIndex]; if (!section || sectionStartIndex === undefined) { return null; } const headerOffset = section.header ? 1 : 0; const dataLastIndex = section.data.length - 1; const localIndex = index - sectionStartIndex - headerOffset; if (item === Dummy) { const isLastDummy = localIndex === dataLastIndex + (section.dummyCount ?? 0); return /*#__PURE__*/_jsx(Dummy.View, { sectionIndex: sectionIndex, disabled: !isLastDummy }); } const isHeader = section.header && index === sectionStartIndex; if (isHeader) { return /*#__PURE__*/_jsx(View, { style: flex1, children: section.header.element }); } const isFooter = section.footer && index === sectionStartIndex + section.data.length + headerOffset + (section.dummyCount ?? 0); if (isFooter) { return /*#__PURE__*/_jsx(View, { style: flex1, children: section.footer.element }); } let style; if (section.gap && containerWidth) { const sectionNumOfColumns = section.numOfColumns ?? 1; const numOfRows = Math.floor((section.data.length - 1) / (section.numOfColumns ?? 1)); const includeEdge = !(typeof section.gap === 'number') && !!section.gap.includeEdge; const gap = typeof section.gap === 'number' ? section.gap : section.gap.size; const numOfGaps = includeEdge ? sectionNumOfColumns + 1 : sectionNumOfColumns - 1; const itemWidth = (containerWidth - contentContainerPaddingHorizontal - numOfGaps * gap) / sectionNumOfColumns; style = { width: itemWidth }; const indexInRow = localIndex % sectionNumOfColumns; style.marginLeft = includeEdge ? gap - gap * indexInRow / sectionNumOfColumns : gap * indexInRow / sectionNumOfColumns; if (numOfRows > 0) { const isLastRow = Math.floor(localIndex / sectionNumOfColumns) === numOfRows; if (!isLastRow) { style.marginBottom = gap; } } } else if (numOfColumns > 1) { style = flex1; } return /*#__PURE__*/_jsx(View, { onLayout: e => { const layout = e.nativeEvent?.layout; if (!layout) return; const { width, height } = layout; if (!width && !height) return; Dummy.emitSize(sectionIndex, localIndex, { width, height }); }, style: style, children: section.renderItem({ index: localIndex, item, ...etc }) }); }, getItemType: (item, index) => { const sectionIndex = binarySearchClosestIndex(sectionStartIndices, index); const section = dataSections[sectionIndex]; const sectionStartIndex = sectionStartIndices[sectionIndex]; if (!section || sectionStartIndex === undefined) { return -1; } if (item === Dummy) { return Dummy.type; } const headerOffset = section.header ? 1 : 0; const isHeader = section.header && index === sectionStartIndex; if (isHeader) { return section.header.type ?? `header-${sectionIndex}`; } const isFooter = section.footer && index === sectionStartIndex + section.data.length + headerOffset + (section.dummyCount ?? 0); if (isFooter) { return section.footer.type ?? `footer-${sectionIndex}`; } return section.type ?? sectionIndex; }, overrideItemLayout: (layout, _, index) => { const sectionIndex = binarySearchClosestIndex(sectionStartIndices, index); const section = dataSections[sectionIndex]; const sectionStartIndex = sectionStartIndices[sectionIndex]; if (!section || sectionStartIndex === undefined) { return; } const headerOffset = section.header ? 1 : 0; const isHeader = section.header && index === sectionStartIndex; const isFooter = section.footer && index === sectionStartIndex + section.data.length + headerOffset + (section.dummyCount ?? 0); if (isHeader) { layout.span = numOfColumns; layout.size = section.header.size; } else if (isFooter) { layout.span = numOfColumns; layout.size = section.footer.size; } else { layout.span = section.numOfColumns ? numOfColumns / section.numOfColumns : numOfColumns; layout.size = section.itemSize; } } }); } return /*#__PURE__*/React.forwardRef(FlashSectionList); }, setFlashList: FlashListComponent => { buildProps.FlashListComponent = FlashListComponent; }, setDummy: DummyClass => { buildProps.DummyClass = DummyClass; }, setLayoutManager: LayoutManagerClass => { buildProps.LayoutManagerClass = LayoutManagerClass; } }; } export default FlashSectionListBuilder().build(); //# sourceMappingURL=FlashSectionList.js.map