UNPKG

react-native-sortables

Version:

Powerful Sortable Components for Flexible Content Reordering in React Native

353 lines (341 loc) 14.6 kB
"use strict"; import { useAnimatedReaction, useDerivedValue, useSharedValue } from 'react-native-reanimated'; import { error, gt as gt_, lt as lt_, reorderInsert } from '../../../../../utils'; import { getAdditionalSwapOffset, useDebugBoundingBox } from '../../../../shared'; import { getSwappedToGroupAfterIndices, getSwappedToGroupBeforeIndices, getTotalGroupSize } from './utils'; const useInsertStrategy = ({ activeItemKey, appliedLayout, calculateFlexLayout, columnGap, fixedItemKeys, flexDirection, indexToKey, itemDimensions, keyToGroup, keyToIndex, rowGap, useFlexLayoutReaction }) => { useAnimatedReaction(() => fixedItemKeys?.value, fixedKeys => { if (Object.keys(fixedKeys ?? {}).length > 0) { throw error('Fixed items are not yet supported in flex layout'); } }); const isColumn = flexDirection.startsWith('column'); const isReverse = flexDirection.endsWith('reverse'); const gt = isReverse ? lt_ : gt_; const lt = isReverse ? gt_ : lt_; let mainCoordinate; let crossCoordinate; let mainDimension; let crossDimension; let mainGap; let crossGap; if (isColumn) { mainCoordinate = 'y'; crossCoordinate = 'x'; mainDimension = 'height'; crossDimension = 'width'; mainGap = rowGap; crossGap = columnGap; } else { mainCoordinate = 'x'; crossCoordinate = 'y'; mainDimension = 'width'; crossDimension = 'height'; mainGap = columnGap; crossGap = rowGap; } const swappedBeforeIndexes = useSharedValue(null); const swappedAfterIndexes = useSharedValue(null); const swappedBeforeLayout = useSharedValue(null); const swappedAfterLayout = useSharedValue(null); const debugBox = useDebugBoundingBox(); const activeGroupIndex = useDerivedValue(() => { const key = activeItemKey.value; if (key === null) return null; return keyToGroup.value[key] ?? null; }); useAnimatedReaction(() => activeItemKey.value !== null && activeGroupIndex.value !== null && appliedLayout.value !== null ? { activeItemIndex: keyToIndex.value[activeItemKey.value], activeItemKey: activeItemKey.value, currentGroupIndex: activeGroupIndex.value, groupSizeLimit: appliedLayout.value.groupSizeLimit, indexToKey: indexToKey.value, itemDimensions: itemDimensions.value, itemGroups: appliedLayout.value.itemGroups, keyToIndex: keyToIndex.value, mainDimension, mainGap: mainGap.value } : null, props => { swappedBeforeIndexes.value = props && getSwappedToGroupBeforeIndices(props); swappedAfterIndexes.value = props && getSwappedToGroupAfterIndices(props); }); useFlexLayoutReaction(useDerivedValue(() => swappedBeforeIndexes.value?.indexToKey ?? null), layout => { 'worklet'; swappedBeforeLayout.value = layout; }); useFlexLayoutReaction(useDerivedValue(() => swappedAfterIndexes.value?.indexToKey ?? null), layout => { 'worklet'; swappedAfterLayout.value = layout; }); return ({ activeIndex, activeKey, dimensions: activeItemDimensions, position }) => { 'worklet'; if (activeGroupIndex.value === null || appliedLayout.value === null) return; let currentLayout = appliedLayout.value; const sharedSwapProps = { activeItemKey: activeKey, groupSizeLimit: currentLayout.groupSizeLimit, indexToKey: indexToKey.value, itemDimensions: itemDimensions.value, itemGroups: currentLayout.itemGroups, keyToIndex: keyToIndex.value, mainDimension, mainGap: mainGap.value }; // CROSS AXIS BOUNDS let beforeIndexes = swappedBeforeIndexes.value; let beforeLayout = swappedBeforeLayout.value; let groupIndex = activeGroupIndex.value; let firstGroupItemIndex = activeIndex; let itemIndexInGroup = 0; const crossAxisPosition = position[crossCoordinate]; // Group before let swapGroupBeforeOffset = Infinity; let swapGroupBeforeBound = Infinity; do { if (!beforeIndexes) { break; } if (swapGroupBeforeBound !== Infinity) { groupIndex = beforeIndexes.groupIndex; firstGroupItemIndex = beforeIndexes.itemIndex; itemIndexInGroup = beforeIndexes.itemIndexInGroup; if (beforeLayout) currentLayout = beforeLayout; beforeIndexes = getSwappedToGroupBeforeIndices({ ...sharedSwapProps, activeItemIndex: activeIndex, currentGroupIndex: groupIndex }); if (!beforeIndexes) break; swappedBeforeLayout.value = calculateFlexLayout(beforeIndexes.indexToKey); } beforeLayout = swappedBeforeLayout.value; swapGroupBeforeOffset = currentLayout.crossAxisGroupOffsets[groupIndex] ?? 0; if (groupIndex === 0) { swapGroupBeforeBound = swapGroupBeforeOffset; } else { const swapOffset = ((beforeLayout?.crossAxisGroupOffsets[beforeIndexes.groupIndex] ?? 0) + swapGroupBeforeOffset + (currentLayout.crossAxisGroupSizes[groupIndex] ?? 0)) / 2; const additionalSwapOffset = getAdditionalSwapOffset(crossGap.value, beforeLayout?.crossAxisGroupSizes?.[beforeIndexes.groupIndex] ?? 0); swapGroupBeforeBound = swapOffset - additionalSwapOffset; } } while (swapGroupBeforeBound > 0 && crossAxisPosition < swapGroupBeforeBound); // Group after let afterIndexes = swappedAfterIndexes.value; let afterLayout = swappedAfterLayout.value; let swappedAfterGroupsCount = swappedAfterLayout.value?.itemGroups.length ?? 0; let swapGroupAfterOffset = -Infinity; let swapGroupAfterBound = -Infinity; do { if (!afterIndexes) { break; } if (swapGroupAfterBound !== -Infinity) { swappedAfterGroupsCount = swappedAfterLayout.value?.itemGroups.length ?? 0; groupIndex = afterIndexes.groupIndex; firstGroupItemIndex = afterIndexes.itemIndex; itemIndexInGroup = afterIndexes.itemIndexInGroup; if (afterLayout) currentLayout = afterLayout; afterIndexes = getSwappedToGroupAfterIndices({ ...sharedSwapProps, activeItemIndex: activeIndex, currentGroupIndex: groupIndex }); if (!afterIndexes) break; swappedAfterLayout.value = calculateFlexLayout(afterIndexes.indexToKey); } afterLayout = swappedAfterLayout.value; const currentGroupBeforeOffset = currentLayout.crossAxisGroupOffsets[groupIndex] ?? 0; swapGroupAfterOffset = currentGroupBeforeOffset + (currentLayout.crossAxisGroupSizes[groupIndex] ?? 0); const afterSwapGroupBeforeOffset = afterLayout?.crossAxisGroupOffsets[afterIndexes.groupIndex] ?? 0; const afterSwapGroupSize = Math.max(afterLayout?.crossAxisGroupSizes?.[afterIndexes.groupIndex] ?? 0, activeItemDimensions[crossDimension]); const swapOffset = afterSwapGroupBeforeOffset ? (currentGroupBeforeOffset + afterSwapGroupBeforeOffset + afterSwapGroupSize) / 2 : swapGroupAfterOffset; const additionalSwapOffset = getAdditionalSwapOffset(crossGap.value, afterLayout?.crossAxisGroupSizes?.[afterIndexes.groupIndex] ?? 0); swapGroupAfterBound = swapOffset + additionalSwapOffset; } while (crossAxisPosition > swapGroupAfterBound && groupIndex < swappedAfterGroupsCount && groupIndex >= activeGroupIndex.value); // MAIN AXIS BOUNDS // currentGroup is the updated group after new layout calculation // that contains the active item const currentGroup = currentLayout.itemGroups[groupIndex]; if (!currentGroup) { const lastItemIndex = indexToKey.value.length - 1; if (activeIndex === lastItemIndex) { return null; } // TODO - add fixed items support in flex return reorderInsert(indexToKey.value, activeIndex, lastItemIndex, undefined); } const mainAxisPosition = position[mainCoordinate]; // Find the itemIndexInGroup of the active item if it is in the same group if (groupIndex === activeGroupIndex.value) { const firstItemKey = currentGroup[0]; if (firstItemKey === undefined) return; const firstItemIndex = keyToIndex.value[firstItemKey]; if (firstItemIndex === undefined) return; itemIndexInGroup = activeIndex - firstItemIndex; } const initialItemIndexInGroup = itemIndexInGroup; // Item after let swapItemAfterOffset = -Infinity; let swapItemAfterBound = -Infinity; do { if (swapItemAfterBound !== -Infinity) { itemIndexInGroup++; } const currentItemKey = currentGroup[itemIndexInGroup]; const currentItemPosition = currentLayout.itemPositions[currentItemKey]; const currentItemDimensions = itemDimensions.value[currentItemKey]; if (!currentItemPosition || !currentItemDimensions) return; swapItemAfterOffset = currentItemPosition[mainCoordinate] + (isReverse ? 0 : currentItemDimensions[mainDimension]); const nextItemKey = currentGroup[itemIndexInGroup + 1]; if (nextItemKey === undefined) { swapItemAfterBound = swapItemAfterOffset; break; } const nextItemPosition = currentLayout.itemPositions[nextItemKey]; const nextItemDimensions = itemDimensions.value[nextItemKey]; if (!nextItemPosition || !nextItemDimensions) break; const currentItemMainAxisPosition = currentItemPosition[mainCoordinate]; const nextItemMainAxisPosition = nextItemPosition[mainCoordinate]; const isCurrentBeforeNext = currentItemMainAxisPosition < nextItemMainAxisPosition; const sizeToAdd = isCurrentBeforeNext ? nextItemDimensions[mainDimension] : currentItemDimensions[mainDimension]; const averageOffset = (currentItemMainAxisPosition + nextItemMainAxisPosition + sizeToAdd) / 2; const additionalSwapOffset = getAdditionalSwapOffset(mainGap.value, sizeToAdd); swapItemAfterBound = averageOffset + (isCurrentBeforeNext ? 1 : -1) * additionalSwapOffset; } while (itemIndexInGroup < currentGroup.length - 1 && gt(mainAxisPosition, swapItemAfterBound)); // Item before let canBeFirst = true; const groupBefore = currentLayout.itemGroups[groupIndex - 1]; if (groupBefore && itemIndexInGroup > 0) { const groupBeforeSize = getTotalGroupSize(groupBefore, itemDimensions.value, mainDimension, mainGap.value); canBeFirst = groupBeforeSize + activeItemDimensions[mainDimension] + mainGap.value > currentLayout.groupSizeLimit; } let swapItemBeforeOffset = Infinity; let swapItemBeforeBound = Infinity; do { if (swapItemBeforeBound !== Infinity) { itemIndexInGroup--; } const currentItemKey = currentGroup[itemIndexInGroup]; const currentItemPosition = currentLayout.itemPositions[currentItemKey]; const currentItemDimensions = itemDimensions.value[currentItemKey]; if (!currentItemPosition || !currentItemDimensions) return; swapItemBeforeOffset = currentItemPosition[mainCoordinate] + (isReverse ? currentItemDimensions[mainDimension] : 0); const prevItemKey = currentGroup[itemIndexInGroup - 1]; if (prevItemKey === undefined) { swapItemBeforeBound = swapItemBeforeOffset; break; } const prevItemPosition = currentLayout.itemPositions[prevItemKey]; const prevItemDimensions = itemDimensions.value[prevItemKey]; if (!prevItemPosition || !prevItemDimensions) return; const currentItemMainAxisPosition = currentItemPosition[mainCoordinate]; const prevItemMainAxisPosition = prevItemPosition[mainCoordinate]; const isPrevBeforeCurrent = prevItemMainAxisPosition < currentItemMainAxisPosition; const sizeToAdd = isPrevBeforeCurrent ? currentItemDimensions[mainDimension] : prevItemDimensions[mainDimension]; const averageOffset = (prevItemMainAxisPosition + currentItemMainAxisPosition + sizeToAdd) / 2; const additionalSwapOffset = getAdditionalSwapOffset(mainGap.value, sizeToAdd); swapItemBeforeBound = averageOffset - (isPrevBeforeCurrent ? 1 : -1) * additionalSwapOffset; } while ( // handle edge case when the active item cannot be the first item of // the current group itemIndexInGroup > (canBeFirst ? 0 : 1) && lt(mainAxisPosition, swapItemBeforeBound)); // DEBUG ONLY if (debugBox) { if (swapGroupAfterOffset > swapGroupAfterBound) { swapGroupAfterOffset = swapGroupAfterBound; } if (swapGroupBeforeOffset < swapGroupBeforeBound) { swapGroupBeforeOffset = swapGroupBeforeBound; } if (swapItemAfterOffset > swapItemAfterBound) { swapItemAfterOffset = swapItemAfterBound; } if (swapItemBeforeOffset < swapItemBeforeBound) { swapItemBeforeOffset = swapItemBeforeBound; } if (isColumn) { debugBox.top.update({ x: swapGroupBeforeBound, y: swapItemBeforeBound }, { x: swapGroupAfterBound, y: swapItemBeforeOffset }); debugBox.bottom.update({ x: swapGroupAfterBound, y: swapItemAfterOffset }, { x: swapGroupBeforeBound, y: swapItemAfterBound }); debugBox.right.update({ x: swapGroupAfterOffset, y: swapItemBeforeBound }, { x: swapGroupAfterBound, y: swapItemAfterBound }); debugBox.left.update({ x: swapGroupBeforeBound, y: swapItemBeforeBound }, { x: swapGroupBeforeOffset, y: swapItemAfterBound }); } else { debugBox.top.update({ x: swapItemBeforeBound, y: swapGroupBeforeBound }, { x: swapItemAfterBound, y: swapGroupBeforeOffset }); debugBox.bottom.update({ x: swapItemBeforeBound, y: swapGroupAfterBound }, { x: swapItemAfterBound, y: swapGroupAfterOffset }); debugBox.right.update({ x: swapItemAfterBound, y: swapGroupBeforeBound }, { x: swapItemAfterOffset, y: swapGroupAfterBound }); debugBox.left.update({ x: swapItemBeforeBound, y: swapGroupBeforeBound }, { x: swapItemBeforeOffset, y: swapGroupAfterBound }); } } const newActiveIndex = firstGroupItemIndex + (itemIndexInGroup - initialItemIndexInGroup); if (newActiveIndex === activeIndex) return; return reorderInsert(indexToKey.value, activeIndex, newActiveIndex, undefined // TODO - add fixed items support in flex ); }; }; export default useInsertStrategy; //# sourceMappingURL=index.js.map