UNPKG

react-native-sortables

Version:

Powerful Sortable Components for Flexible Content Reordering in React Native

268 lines (245 loc) 8.39 kB
import { type SharedValue, useDerivedValue } from 'react-native-reanimated'; import type { Coordinate, Dimension, ReorderFunction, SortStrategyFactory } from '../../../../types'; import { getAdditionalSwapOffset, useCommonValuesContext, useCustomHandleContext, useDebugBoundingBox } from '../../../shared'; import { useAutoOffsetAdjustmentContext } from '../../AutoOffsetAdjustmentProvider'; import { useGridLayoutContext } from '../GridLayoutProvider'; import { calculateLayout, getCrossIndex, getMainIndex } from '../utils'; export const createGridStrategy = ( useInactiveIndexToKey: () => SharedValue<Array<string>>, reorder: ReorderFunction ): SortStrategyFactory => () => { const { containerHeight, containerWidth, indexToKey, itemHeights, itemWidths } = useCommonValuesContext(); const { crossGap, isVertical, mainGap, mainGroupSize, numGroups } = useGridLayoutContext(); const { additionalCrossOffset } = useAutoOffsetAdjustmentContext() ?? {}; const { fixedItemKeys } = useCustomHandleContext() ?? {}; const othersIndexToKey = useInactiveIndexToKey(); const debugBox = useDebugBoundingBox(); const othersLayout = useDerivedValue(() => additionalCrossOffset?.value === null ? null : calculateLayout({ gaps: { cross: crossGap.value, main: mainGap.value }, indexToKey: othersIndexToKey.value, isVertical, itemHeights: itemHeights.value, itemWidths: itemWidths.value, numGroups, startCrossOffset: additionalCrossOffset?.value }) ); let mainContainerSize: SharedValue<null | number>; let crossContainerSize: SharedValue<null | number>; let mainCoordinate: Coordinate; let crossCoordinate: Coordinate; let crossDimension: Dimension; if (isVertical) { mainContainerSize = containerWidth; crossContainerSize = containerHeight; mainCoordinate = 'x'; crossCoordinate = 'y'; crossDimension = 'height'; } else { mainContainerSize = containerHeight; crossContainerSize = containerWidth; mainCoordinate = 'y'; crossCoordinate = 'x'; crossDimension = 'width'; } return ({ activeIndex, dimensions, position }) => { 'worklet'; if ( !othersLayout.value || crossContainerSize.value === null || mainContainerSize.value === null || mainGroupSize.value === null ) { return; } const { crossAxisOffsets } = othersLayout.value; const startCrossIndex = getCrossIndex(activeIndex, numGroups); const startMainIndex = getMainIndex(activeIndex, numGroups); const startCrossSize = dimensions[crossDimension]; let crossIndex = startCrossIndex; let mainIndex = startMainIndex; const getItemCrossSize = (index: number) => crossAxisOffsets[index] !== undefined && crossAxisOffsets[index + 1] !== undefined ? crossAxisOffsets[index + 1]! - crossAxisOffsets[index] - crossGap.value : null; // CROSS AXIS BOUNDS // Before bound let crossBeforeOffset = -Infinity; let crossBeforeBound = Infinity; let crossCurrentSize = startCrossSize; do { if (crossBeforeBound !== Infinity) { crossIndex--; } crossBeforeOffset = crossAxisOffsets[crossIndex] ?? 0; const crossBeforeSize = getItemCrossSize(crossIndex - 1); if (crossBeforeSize) { const swapOffset = ((crossAxisOffsets[crossIndex - 1] ?? 0) + crossBeforeOffset + crossCurrentSize) / 2; const additionalBeforeOffset = getAdditionalSwapOffset(crossBeforeSize); crossBeforeBound = swapOffset - additionalBeforeOffset; crossCurrentSize = crossBeforeSize; } else { crossBeforeBound = 0; } } while ( crossBeforeBound > 0 && position[crossCoordinate] < crossBeforeBound ); // After bound let crossAfterOffset = Infinity; let crossAfterBound = -Infinity; crossCurrentSize = startCrossSize; do { if (crossAfterBound !== -Infinity) { crossIndex++; } const nextCrossAxisOffset = crossAxisOffsets[crossIndex + 1]; if (!nextCrossAxisOffset) { break; } crossAfterOffset = (crossAxisOffsets[crossIndex] ?? 0) + crossCurrentSize; const crossAfterSize = getItemCrossSize(crossIndex + 1); const swapOffset = (crossAfterOffset + nextCrossAxisOffset) / 2; const additionalAfterOffset = getAdditionalSwapOffset(crossAfterSize); crossAfterBound = swapOffset + additionalAfterOffset; if (crossAfterSize) { crossCurrentSize = crossAfterSize; } } while ( crossAfterBound < crossContainerSize.value && position[crossCoordinate] > crossAfterBound ); // MAIN AXIS BOUNDS const additionalOffset = getAdditionalSwapOffset(mainContainerSize.value); // Before bound let mainBeforeOffset = -Infinity; let mainBeforeBound = Infinity; do { if (mainBeforeBound !== Infinity) { mainIndex--; } mainBeforeOffset = mainIndex * (mainGroupSize.value + mainGap.value); mainBeforeBound = mainBeforeOffset - additionalOffset; } while ( mainBeforeBound > 0 && position[mainCoordinate] < mainBeforeBound ); // After bound let mainAfterOffset = Infinity; let mainAfterBound = -Infinity; do { if (mainAfterBound !== -Infinity) { mainIndex++; } mainAfterOffset = mainIndex * (mainGroupSize.value + mainGap.value) + mainGroupSize.value; mainAfterBound = mainAfterOffset + additionalOffset; } while ( mainAfterBound < mainContainerSize.value && position[mainCoordinate] > mainAfterBound ); // DEBUG ONLY if (debugBox) { if (isVertical) { debugBox.top.update( { x: mainBeforeBound, y: crossBeforeBound }, { x: mainAfterBound, y: Math.max(crossBeforeOffset, crossBeforeBound) } ); debugBox.bottom.update( { x: mainBeforeBound, y: Math.min(crossAfterOffset, crossAfterBound) }, { x: mainAfterBound, y: crossAfterBound } ); debugBox.left.update( { x: mainBeforeBound, y: crossBeforeBound }, { x: mainBeforeOffset, y: crossAfterBound } ); debugBox.right.update( { x: mainAfterOffset, y: crossBeforeBound }, { x: mainAfterBound, y: crossAfterBound } ); } else { debugBox.top.update( { x: crossBeforeBound, y: mainBeforeBound }, { x: crossAfterBound, y: mainBeforeOffset } ); debugBox.bottom.update( { x: crossBeforeBound, y: mainAfterBound }, { x: crossAfterBound, y: mainAfterOffset } ); debugBox.left.update( { x: crossBeforeBound, y: mainBeforeBound }, { x: Math.max(crossBeforeOffset, crossBeforeBound), y: mainAfterBound } ); debugBox.right.update( { x: Math.min(crossAfterOffset, crossAfterBound), y: mainAfterBound }, { x: crossAfterBound, y: mainBeforeBound } ); } } const idxToKey = indexToKey.value; const itemsCount = idxToKey.length; const limitedCrossIndex = Math.max( 0, Math.min(crossIndex, Math.floor((itemsCount - 1) / numGroups)) ); const newIndex = Math.min( Math.max(0, limitedCrossIndex * numGroups + mainIndex), itemsCount - 1 ); if ( newIndex === activeIndex || fixedItemKeys?.value[idxToKey[newIndex]!] ) { return; } // return the new order of items return reorder(idxToKey, activeIndex, newIndex, fixedItemKeys?.value); }; };