flash-section-list
Version:
SectionList base on FlashList
297 lines (296 loc) • 13 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.FlashSectionListBuilder = FlashSectionListBuilder;
exports.default = void 0;
var _flashList = require("@shopify/flash-list");
var _react = _interopRequireWildcard(require("react"));
var _utils = require("./utils.js");
var _reactNative = require("react-native");
var _useDummy = require("./useDummy.js");
var _Dummy = _interopRequireDefault(require("./Dummy.js"));
var _LayoutManager = _interopRequireDefault(require("./LayoutManager.js"));
var _jsxRuntime = require("react/jsx-runtime");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const methodNames = ['prepareForLayoutAnimationRender', 'recordInteraction', 'recomputeViewableItems', 'scrollToEnd', 'scrollToIndex', 'scrollToItem', 'scrollToOffset'];
const isElementSection = section => {
return /*#__PURE__*/_react.default.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'];
function FlashSectionListBuilder() {
const buildProps = {
FlashListComponent: _flashList.FlashList,
DummyClass: _Dummy.default,
LayoutManagerClass: _LayoutManager.default
};
return {
build: () => {
const FlashListComponent = buildProps.FlashListComponent;
const DummyClass = buildProps.DummyClass;
const LayoutManagerClass = buildProps.LayoutManagerClass;
function FlashSectionList(propsOrigin, ref) {
const flashlist = (0, _react.useRef)(null);
const [containerWidth, setContainerWidth] = (0, _react.useState)(undefined);
let {
sections,
...props
} = propsOrigin;
props = (0, _utils.omit)(propsOrigin, omitProps, false);
const contentContainerPaddingHorizontal = (0, _react.useMemo)(() => {
const paddingHorizontal = (0, _utils.findFirstProp)(props.contentContainerStyle, ['paddingHorizontal', 'paddingLeft']);
return typeof paddingHorizontal === 'number' ? paddingHorizontal * 2 : 0;
}, [props.contentContainerStyle]);
const Dummy = (0, _useDummy.useDummy)({
horizontal: props.horizontal,
DummyClass,
LayoutManagerClass
});
const {
dataSections,
sectionStartIndices,
serializedData,
stickyHeaderIndices,
numOfColumns
} = (0, _react.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: (0, _utils.lcm)(numOfColumnArray)
};
}, [Dummy, sections]);
(0, _react.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__*/(0, _jsxRuntime.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 = (0, _utils.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__*/(0, _jsxRuntime.jsx)(Dummy.View, {
sectionIndex: sectionIndex,
disabled: !isLastDummy
});
}
const isHeader = section.header && index === sectionStartIndex;
if (isHeader) {
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: flex1,
children: section.header.element
});
}
const isFooter = section.footer && index === sectionStartIndex + section.data.length + headerOffset + (section.dummyCount ?? 0);
if (isFooter) {
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.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__*/(0, _jsxRuntime.jsx)(_reactNative.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 = (0, _utils.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 = (0, _utils.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.default.forwardRef(FlashSectionList);
},
setFlashList: FlashListComponent => {
buildProps.FlashListComponent = FlashListComponent;
},
setDummy: DummyClass => {
buildProps.DummyClass = DummyClass;
},
setLayoutManager: LayoutManagerClass => {
buildProps.LayoutManagerClass = LayoutManagerClass;
}
};
}
var _default = exports.default = FlashSectionListBuilder().build();
//# sourceMappingURL=FlashSectionList.js.map
;