UNPKG

react-native-sortables

Version:

Powerful Sortable Components for Flexible Content Reordering in React Native

298 lines (290 loc) 9.97 kB
"use strict"; import { IS_WEB } from '../../../../constants'; import { resolveDimension, reverseArray, sum } from '../../../../utils'; const createGroups = (indexToKey, mainItemSizes, crossItemSizes, gap, groupMainSizeLimit) => { 'worklet'; const groups = []; const crossAxisGroupSizes = []; let currentGroup = []; let totalGroupItemsMainSize = 0; let groupCrossSize = 0; for (const key of indexToKey) { const mainItemDimension = resolveDimension(mainItemSizes, key); const crossItemDimension = resolveDimension(crossItemSizes, key); if (mainItemDimension === null || crossItemDimension === null) { return null; } const currentTotalSize = totalGroupItemsMainSize + currentGroup.length * gap + mainItemDimension; if (currentTotalSize > groupMainSizeLimit + 0.1) { groups.push(currentGroup); crossAxisGroupSizes.push(groupCrossSize); currentGroup = []; totalGroupItemsMainSize = 0; groupCrossSize = 0; } currentGroup.push(key); totalGroupItemsMainSize += mainItemDimension; if (crossItemDimension > groupCrossSize) { groupCrossSize = crossItemDimension; } } if (currentGroup.length > 0) { groups.push(currentGroup); crossAxisGroupSizes.push(groupCrossSize); } return { crossAxisGroupSizes, groups }; }; const calculateAlignment = (align, sizes, minSize, maxSize, shouldWrap, providedGap = 0) => { 'worklet'; let startOffset = 0; let adjustedGap = providedGap; const getTotalSize = gap => sum(sizes) + gap * (sizes.length - 1); const totalSize = getTotalSize(providedGap); const clampedTotalSize = Math.min(Math.max(getTotalSize(providedGap), minSize), maxSize); switch (align) { case 'center': startOffset = (clampedTotalSize - totalSize) / 2; break; case 'flex-end': startOffset = clampedTotalSize - totalSize; break; case 'space-around': if (sizes.length > 1 || shouldWrap) { adjustedGap = Math.max((clampedTotalSize - sum(sizes) + providedGap) / sizes.length, providedGap); if (adjustedGap > providedGap) { startOffset = (clampedTotalSize - getTotalSize(adjustedGap)) / 2; } } break; case 'space-between': if (sizes.length > 1 || shouldWrap) { adjustedGap = Math.max((clampedTotalSize - sum(sizes)) / (sizes.length - 1), providedGap); } break; case 'space-evenly': if (sizes.length > 1 || shouldWrap) { adjustedGap = Math.max((clampedTotalSize - sum(sizes) + 2 * providedGap) / (sizes.length + 1), providedGap); if (adjustedGap > providedGap) { startOffset = (clampedTotalSize - getTotalSize(adjustedGap)) / 2; } } break; } const offsets = [startOffset]; for (let i = 0; i < sizes.length - 1; i++) { offsets.push(startOffset += (sizes[i] ?? 0) + adjustedGap); } return { adjustedGap, offsets, totalSize: clampedTotalSize }; }; const handleLayoutCalculation = (groups, crossAxisGroupSizes, mainItemSizes, crossItemSizes, gaps, axisDirections, { alignContent, alignItems, justifyContent }, paddings, limits, isReverse, shouldWrap) => { 'worklet'; const isRow = axisDirections.main === 'row'; const expandMultiGroup = !IS_WEB && groups.length > 1; // expands to max height/width const paddingHorizontal = paddings.left + paddings.right; const paddingVertical = paddings.top + paddings.bottom; let minMainContainerSize; let maxMainContainerSize; let minCrossContainerSize; let maxCrossContainerSize; if (isRow) { minMainContainerSize = limits.minWidth - paddingHorizontal; maxMainContainerSize = limits.maxWidth - paddingHorizontal; minCrossContainerSize = limits.minHeight - paddingVertical; maxCrossContainerSize = limits.maxHeight - paddingVertical; } else { minMainContainerSize = limits.minHeight - paddingVertical; maxMainContainerSize = limits.maxHeight - paddingVertical; minCrossContainerSize = limits.minWidth - paddingHorizontal; maxCrossContainerSize = limits.maxWidth - paddingHorizontal; } // ALIGN CONTENT // position groups on the cross axis const contentAlignment = calculateAlignment(alignContent, crossAxisGroupSizes, minCrossContainerSize, maxCrossContainerSize, shouldWrap, gaps[axisDirections.main]); let totalHeight = 0; let totalWidth = 0; if (isRow) { totalHeight = contentAlignment.totalSize + paddingVertical; totalWidth = expandMultiGroup ? limits.maxWidth : limits.minWidth; } else { totalHeight = expandMultiGroup ? limits.maxHeight : limits.minHeight; totalWidth = contentAlignment.totalSize + paddingHorizontal; } const itemPositions = {}; for (let i = 0; i < groups.length; i++) { // JUSTIFY CONTENT // position items in groups on the main axis const group = groups[i]; const groupCrossSize = crossAxisGroupSizes[i]; const groupCrossOffset = contentAlignment.offsets[i]; const mainAxisGroupItemSizes = []; for (const key of group) { const mainItemSize = resolveDimension(mainItemSizes, key); if (mainItemSize === null) { return null; } mainAxisGroupItemSizes.push(mainItemSize); } const contentJustification = calculateAlignment(justifyContent, mainAxisGroupItemSizes, expandMultiGroup ? maxMainContainerSize : minMainContainerSize, maxMainContainerSize, shouldWrap, gaps[axisDirections.cross]); if (!expandMultiGroup) { if (isRow) { totalWidth = Math.max(totalWidth, contentJustification.totalSize + paddingHorizontal); } else { totalHeight = Math.max(totalHeight, contentJustification.totalSize + paddingVertical); } } for (let j = 0; j < group.length; j++) { // ALIGN ITEMS // TODO - override with alignSelf if specified for an item // position items in groups on the cross axis const key = group[j]; const crossItemSize = resolveDimension(crossItemSizes, key); if (crossItemSize === null) { return null; } const itemAlignment = calculateAlignment(alignItems, [crossItemSize], groupCrossSize, groupCrossSize, shouldWrap); const crossAxisPosition = groupCrossOffset + itemAlignment.offsets[0]; const mainAxisPosition = contentJustification.offsets[j]; if (isRow && isReverse) { // row-reverse itemPositions[key] = { x: totalWidth - mainAxisPosition - mainAxisGroupItemSizes[j] - paddings.right, y: crossAxisPosition + paddings.top }; } else if (isRow) { // row itemPositions[key] = { x: mainAxisPosition + paddings.left, y: crossAxisPosition + paddings.top }; } else if (isReverse) { // column-reverse itemPositions[key] = { x: crossAxisPosition + paddings.left, y: totalHeight - mainAxisPosition - mainAxisGroupItemSizes[j] - paddings.bottom }; } else { // column itemPositions[key] = { x: crossAxisPosition + paddings.left, y: mainAxisPosition + paddings.top }; } } } let additionalOffset = 0; if (isRow && isReverse) { // row-reverse additionalOffset = paddings.bottom; } else if (isRow) { // row additionalOffset = paddings.top; } else if (isReverse) { // column-reverse additionalOffset = paddings.right; } else { // column additionalOffset = paddings.left; } const crossAxisGroupOffsets = contentAlignment.offsets.map(offset => offset + additionalOffset); return { adjustedCrossGap: contentAlignment.adjustedGap, crossAxisGroupOffsets, itemPositions, totalDimensions: { height: totalHeight, width: totalWidth } }; }; export const calculateLayout = ({ flexAlignments, flexDirection, flexWrap, gaps, indexToKey, itemHeights, itemWidths, limits, paddings }) => { 'worklet'; if (!limits) { return null; } // CREATE GROUPS // Determine the direction of the main axis and the parallel dimension const isRow = flexDirection.startsWith('row'); const axisDirections = isRow ? { cross: 'column', main: 'row' } : { cross: 'row', main: 'column' }; let crossItemSizes, mainItemSizes; if (isRow) { mainItemSizes = itemWidths; crossItemSizes = itemHeights; } else { mainItemSizes = itemHeights; crossItemSizes = itemWidths; } const shouldWrap = flexWrap !== 'nowrap'; let groupSizeLimit = Infinity; if (shouldWrap) { if (isRow) { groupSizeLimit = limits.maxWidth - paddings.left - paddings.right; } else { groupSizeLimit = limits.maxHeight - paddings.top - paddings.bottom; } } if (groupSizeLimit <= 0) { return null; } const groupingResult = createGroups(indexToKey, mainItemSizes, crossItemSizes, gaps[axisDirections.cross], groupSizeLimit); if (!groupingResult) { return null; } const { crossAxisGroupSizes, groups } = groupingResult; if (flexWrap === 'wrap-reverse') { reverseArray(groups); reverseArray(crossAxisGroupSizes); } // CALCULATE LAYOUT // based on item groups, gaps and alignment const isReverse = flexDirection.endsWith('reverse'); const layoutResult = handleLayoutCalculation(groups, crossAxisGroupSizes, mainItemSizes, crossItemSizes, gaps, axisDirections, flexAlignments, paddings, limits, isReverse, shouldWrap); if (!layoutResult) { return null; } return { adjustedCrossGap: layoutResult.adjustedCrossGap, contentBounds: [{ x: 0, y: 0 }, { x: layoutResult.totalDimensions.width, y: layoutResult.totalDimensions.height }], crossAxisGroupOffsets: layoutResult.crossAxisGroupOffsets, crossAxisGroupSizes, groupSizeLimit, itemGroups: groups, itemPositions: layoutResult.itemPositions, totalDimensions: layoutResult.totalDimensions }; }; //# sourceMappingURL=layout.js.map