UNPKG

react-native-web

Version:
384 lines (379 loc) 15.3 kB
import _extends from "@babel/runtime/helpers/extends"; import _createForOfIteratorHelperLoose from "@babel/runtime/helpers/createForOfIteratorHelperLoose"; import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/objectWithoutPropertiesLoose"; import _objectSpread from "@babel/runtime/helpers/objectSpread2"; var _excluded = ["ItemSeparatorComponent", "SectionSeparatorComponent", "renderItem", "renderSectionFooter", "renderSectionHeader", "sections", "stickySectionHeadersEnabled"]; /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * * @format */ import View from '../../../exports/View'; import VirtualizedList from '../VirtualizedList'; import { keyExtractor as defaultKeyExtractor } from '../VirtualizeUtils'; import invariant from 'fbjs/lib/invariant'; import * as React from 'react'; /** * Right now this just flattens everything into one list and uses VirtualizedList under the * hood. The only operation that might not scale well is concatting the data arrays of all the * sections when new props are received, which should be plenty fast for up to ~10,000 items. */ class VirtualizedSectionList extends React.PureComponent { constructor() { super(...arguments); this._keyExtractor = (item, index) => { var info = this._subExtractor(index); return info && info.key || String(index); }; this._convertViewable = viewable => { var _info$index; invariant(viewable.index != null, 'Received a broken ViewToken'); var info = this._subExtractor(viewable.index); if (!info) { return null; } var keyExtractorWithNullableIndex = info.section.keyExtractor; var keyExtractorWithNonNullableIndex = this.props.keyExtractor || defaultKeyExtractor; var key = keyExtractorWithNullableIndex != null ? keyExtractorWithNullableIndex(viewable.item, info.index) : keyExtractorWithNonNullableIndex(viewable.item, (_info$index = info.index) !== null && _info$index !== void 0 ? _info$index : 0); return _objectSpread(_objectSpread({}, viewable), {}, { index: info.index, key, section: info.section }); }; this._onViewableItemsChanged = _ref => { var viewableItems = _ref.viewableItems, changed = _ref.changed; var onViewableItemsChanged = this.props.onViewableItemsChanged; if (onViewableItemsChanged != null) { onViewableItemsChanged({ viewableItems: viewableItems.map(this._convertViewable, this).filter(Boolean), changed: changed.map(this._convertViewable, this).filter(Boolean) }); } }; this._renderItem = listItemCount => // eslint-disable-next-line react/no-unstable-nested-components _ref2 => { var item = _ref2.item, index = _ref2.index; var info = this._subExtractor(index); if (!info) { return null; } var infoIndex = info.index; if (infoIndex == null) { var section = info.section; if (info.header === true) { var renderSectionHeader = this.props.renderSectionHeader; return renderSectionHeader ? renderSectionHeader({ section }) : null; } else { var renderSectionFooter = this.props.renderSectionFooter; return renderSectionFooter ? renderSectionFooter({ section }) : null; } } else { var renderItem = info.section.renderItem || this.props.renderItem; var SeparatorComponent = this._getSeparatorComponent(index, info, listItemCount); invariant(renderItem, 'no renderItem!'); return /*#__PURE__*/React.createElement(ItemWithSeparator, { SeparatorComponent: SeparatorComponent, LeadingSeparatorComponent: infoIndex === 0 ? this.props.SectionSeparatorComponent : undefined, cellKey: info.key, index: infoIndex, item: item, leadingItem: info.leadingItem, leadingSection: info.leadingSection, prevCellKey: (this._subExtractor(index - 1) || {}).key // Callback to provide updateHighlight for this item , setSelfHighlightCallback: this._setUpdateHighlightFor, setSelfUpdatePropsCallback: this._setUpdatePropsFor // Provide child ability to set highlight/updateProps for previous item using prevCellKey , updateHighlightFor: this._updateHighlightFor, updatePropsFor: this._updatePropsFor, renderItem: renderItem, section: info.section, trailingItem: info.trailingItem, trailingSection: info.trailingSection, inverted: !!this.props.inverted }); } }; this._updatePropsFor = (cellKey, value) => { var updateProps = this._updatePropsMap[cellKey]; if (updateProps != null) { updateProps(value); } }; this._updateHighlightFor = (cellKey, value) => { var updateHighlight = this._updateHighlightMap[cellKey]; if (updateHighlight != null) { updateHighlight(value); } }; this._setUpdateHighlightFor = (cellKey, updateHighlightFn) => { if (updateHighlightFn != null) { this._updateHighlightMap[cellKey] = updateHighlightFn; } else { // $FlowFixMe[prop-missing] delete this._updateHighlightFor[cellKey]; } }; this._setUpdatePropsFor = (cellKey, updatePropsFn) => { if (updatePropsFn != null) { this._updatePropsMap[cellKey] = updatePropsFn; } else { delete this._updatePropsMap[cellKey]; } }; this._updateHighlightMap = {}; this._updatePropsMap = {}; this._captureRef = ref => { this._listRef = ref; }; } scrollToLocation(params) { var index = params.itemIndex; for (var i = 0; i < params.sectionIndex; i++) { index += this.props.getItemCount(this.props.sections[i].data) + 2; } var viewOffset = params.viewOffset || 0; if (this._listRef == null) { return; } if (params.itemIndex > 0 && this.props.stickySectionHeadersEnabled) { var frame = this._listRef.__getFrameMetricsApprox(index - params.itemIndex, this._listRef.props); viewOffset += frame.length; } var toIndexParams = _objectSpread(_objectSpread({}, params), {}, { viewOffset, index }); // $FlowFixMe[incompatible-use] this._listRef.scrollToIndex(toIndexParams); } getListRef() { return this._listRef; } render() { var _this$props = this.props, ItemSeparatorComponent = _this$props.ItemSeparatorComponent, SectionSeparatorComponent = _this$props.SectionSeparatorComponent, _renderItem = _this$props.renderItem, renderSectionFooter = _this$props.renderSectionFooter, renderSectionHeader = _this$props.renderSectionHeader, _sections = _this$props.sections, stickySectionHeadersEnabled = _this$props.stickySectionHeadersEnabled, passThroughProps = _objectWithoutPropertiesLoose(_this$props, _excluded); var listHeaderOffset = this.props.ListHeaderComponent ? 1 : 0; var stickyHeaderIndices = this.props.stickySectionHeadersEnabled ? [] : undefined; var itemCount = 0; for (var _iterator = _createForOfIteratorHelperLoose(this.props.sections), _step; !(_step = _iterator()).done;) { var section = _step.value; // Track the section header indices if (stickyHeaderIndices != null) { stickyHeaderIndices.push(itemCount + listHeaderOffset); } // Add two for the section header and footer. itemCount += 2; itemCount += this.props.getItemCount(section.data); } var renderItem = this._renderItem(itemCount); return /*#__PURE__*/React.createElement(VirtualizedList, _extends({}, passThroughProps, { keyExtractor: this._keyExtractor, stickyHeaderIndices: stickyHeaderIndices, renderItem: renderItem, data: this.props.sections, getItem: (sections, index) => this._getItem(this.props, sections, index), getItemCount: () => itemCount, onViewableItemsChanged: this.props.onViewableItemsChanged ? this._onViewableItemsChanged : undefined, ref: this._captureRef })); } _getItem(props, sections, index) { if (!sections) { return null; } var itemIdx = index - 1; for (var i = 0; i < sections.length; i++) { var section = sections[i]; var sectionData = section.data; var itemCount = props.getItemCount(sectionData); if (itemIdx === -1 || itemIdx === itemCount) { // We intend for there to be overflow by one on both ends of the list. // This will be for headers and footers. When returning a header or footer // item the section itself is the item. return section; } else if (itemIdx < itemCount) { // If we are in the bounds of the list's data then return the item. return props.getItem(sectionData, itemIdx); } else { itemIdx -= itemCount + 2; // Add two for the header and footer } } return null; } // $FlowFixMe[missing-local-annot] _subExtractor(index) { var itemIndex = index; var _this$props2 = this.props, getItem = _this$props2.getItem, getItemCount = _this$props2.getItemCount, keyExtractor = _this$props2.keyExtractor, sections = _this$props2.sections; for (var i = 0; i < sections.length; i++) { var section = sections[i]; var sectionData = section.data; var key = section.key || String(i); itemIndex -= 1; // The section adds an item for the header if (itemIndex >= getItemCount(sectionData) + 1) { itemIndex -= getItemCount(sectionData) + 1; // The section adds an item for the footer. } else if (itemIndex === -1) { return { section, key: key + ':header', index: null, header: true, trailingSection: sections[i + 1] }; } else if (itemIndex === getItemCount(sectionData)) { return { section, key: key + ':footer', index: null, header: false, trailingSection: sections[i + 1] }; } else { var extractor = section.keyExtractor || keyExtractor || defaultKeyExtractor; return { section, key: key + ':' + extractor(getItem(sectionData, itemIndex), itemIndex), index: itemIndex, leadingItem: getItem(sectionData, itemIndex - 1), leadingSection: sections[i - 1], trailingItem: getItem(sectionData, itemIndex + 1), trailingSection: sections[i + 1] }; } } } _getSeparatorComponent(index, info, listItemCount) { info = info || this._subExtractor(index); if (!info) { return null; } var ItemSeparatorComponent = info.section.ItemSeparatorComponent || this.props.ItemSeparatorComponent; var SectionSeparatorComponent = this.props.SectionSeparatorComponent; var isLastItemInList = index === listItemCount - 1; var isLastItemInSection = info.index === this.props.getItemCount(info.section.data) - 1; if (SectionSeparatorComponent && isLastItemInSection) { return SectionSeparatorComponent; } if (ItemSeparatorComponent && !isLastItemInSection && !isLastItemInList) { return ItemSeparatorComponent; } return null; } } function ItemWithSeparator(props) { var LeadingSeparatorComponent = props.LeadingSeparatorComponent, SeparatorComponent = props.SeparatorComponent, cellKey = props.cellKey, prevCellKey = props.prevCellKey, setSelfHighlightCallback = props.setSelfHighlightCallback, updateHighlightFor = props.updateHighlightFor, setSelfUpdatePropsCallback = props.setSelfUpdatePropsCallback, updatePropsFor = props.updatePropsFor, item = props.item, index = props.index, section = props.section, inverted = props.inverted; var _React$useState = React.useState(false), leadingSeparatorHiglighted = _React$useState[0], setLeadingSeparatorHighlighted = _React$useState[1]; var _React$useState2 = React.useState(false), separatorHighlighted = _React$useState2[0], setSeparatorHighlighted = _React$useState2[1]; var _React$useState3 = React.useState({ leadingItem: props.leadingItem, leadingSection: props.leadingSection, section: props.section, trailingItem: props.item, trailingSection: props.trailingSection }), leadingSeparatorProps = _React$useState3[0], setLeadingSeparatorProps = _React$useState3[1]; var _React$useState4 = React.useState({ leadingItem: props.item, leadingSection: props.leadingSection, section: props.section, trailingItem: props.trailingItem, trailingSection: props.trailingSection }), separatorProps = _React$useState4[0], setSeparatorProps = _React$useState4[1]; React.useEffect(() => { setSelfHighlightCallback(cellKey, setSeparatorHighlighted); // $FlowFixMe[incompatible-call] setSelfUpdatePropsCallback(cellKey, setSeparatorProps); return () => { setSelfUpdatePropsCallback(cellKey, null); setSelfHighlightCallback(cellKey, null); }; }, [cellKey, setSelfHighlightCallback, setSeparatorProps, setSelfUpdatePropsCallback]); var separators = { highlight: () => { setLeadingSeparatorHighlighted(true); setSeparatorHighlighted(true); if (prevCellKey != null) { updateHighlightFor(prevCellKey, true); } }, unhighlight: () => { setLeadingSeparatorHighlighted(false); setSeparatorHighlighted(false); if (prevCellKey != null) { updateHighlightFor(prevCellKey, false); } }, updateProps: (select, newProps) => { if (select === 'leading') { if (LeadingSeparatorComponent != null) { setLeadingSeparatorProps(_objectSpread(_objectSpread({}, leadingSeparatorProps), newProps)); } else if (prevCellKey != null) { // update the previous item's separator updatePropsFor(prevCellKey, _objectSpread(_objectSpread({}, leadingSeparatorProps), newProps)); } } else if (select === 'trailing' && SeparatorComponent != null) { setSeparatorProps(_objectSpread(_objectSpread({}, separatorProps), newProps)); } } }; var element = props.renderItem({ item, index, section, separators }); var leadingSeparator = LeadingSeparatorComponent != null && /*#__PURE__*/React.createElement(LeadingSeparatorComponent, _extends({ highlighted: leadingSeparatorHiglighted }, leadingSeparatorProps)); var separator = SeparatorComponent != null && /*#__PURE__*/React.createElement(SeparatorComponent, _extends({ highlighted: separatorHighlighted }, separatorProps)); return leadingSeparator || separator ? /*#__PURE__*/React.createElement(View, null, inverted === false ? leadingSeparator : separator, element, inverted === false ? separator : leadingSeparator) : element; } /* $FlowFixMe[class-object-subtyping] added when improving typing for this * parameters */ // $FlowFixMe[method-unbinding] export default VirtualizedSectionList;