react-native-sortables
Version:
Powerful Sortable Components for Flexible Content Reordering in React Native
163 lines (155 loc) • 4.94 kB
JavaScript
;
import { useCallback } from 'react';
import { useAnimatedReaction, useDerivedValue } from 'react-native-reanimated';
import { IS_WEB } from '../../../constants';
import { useDebugContext } from '../../../debug';
import { useAnimatableValue, useMutableValue } from '../../../integrations/reanimated';
import { useCommonValuesContext, useMeasurementsContext } from '../../shared';
import { createProvider } from '../../utils';
import { calculateLayout } from './utils';
const DEBUG_COLORS = {
backgroundColor: '#ffa500',
borderColor: '#825500'
};
const {
GridLayoutProvider,
useGridLayoutContext
} = createProvider('GridLayout')(({
columnGap: columnGap_,
isVertical,
numGroups,
numItems,
rowGap: rowGap_,
rowHeight
}) => {
const {
containerWidth,
indexToKey,
itemHeights,
itemPositions,
itemWidths,
shouldAnimateLayout
} = useCommonValuesContext();
const {
applyControlledContainerDimensions
} = useMeasurementsContext();
const debugContext = useDebugContext();
const debugMainGapRects = debugContext?.useDebugRects(numGroups - 1);
const debugCrossGapRects = debugContext?.useDebugRects(Math.ceil(numItems / numGroups) - 1);
const columnGap = useAnimatableValue(columnGap_);
const rowGap = useAnimatableValue(rowGap_);
const mainGap = isVertical ? columnGap : rowGap;
const crossGap = isVertical ? rowGap : columnGap;
/**
* Size of the group of items determined by the parent container size.
* width - in vertical orientation (default) (columns are groups)
* height - in horizontal orientation (rows are groups)
*/
const mainGroupSize = useMutableValue(rowHeight ?? null);
// MAIN GROUP SIZE UPDATER
useAnimatedReaction(() => {
if (!isVertical) {
// TODO - maybe don't require specifying rowHeight (and instead
// occupy the entire height of the container)
return rowHeight ?? null;
}
return containerWidth.value ? (containerWidth.value + mainGap.value) / numGroups - mainGap.value : null;
}, value => {
if (!value) {
return;
}
mainGroupSize.value = value;
if (isVertical) {
itemWidths.value = value;
} else {
itemHeights.value = value;
}
// DEBUG ONLY
if (debugMainGapRects) {
const gap = mainGap.value;
for (let i = 0; i < numGroups - 1; i++) {
const pos = value * (i + 1) + gap * i;
debugMainGapRects[i]?.set({
...DEBUG_COLORS,
...(isVertical ? {
width: gap,
x: pos
} : {
height: gap,
y: pos
})
});
}
}
});
const useGridLayoutReaction = useCallback((idxToKey, onChange) => useAnimatedReaction(() => ({
gaps: {
cross: crossGap.value,
main: mainGap.value
},
indexToKey: idxToKey.value,
isVertical,
itemHeights: itemHeights.value,
itemWidths: itemWidths.value,
mainGroupSize: mainGroupSize.value,
numGroups
}), (props, previousProps) => {
onChange(calculateLayout(props),
// On web, animate layout only if parent container is not resized
// (e.g. skip animation when the browser window is resized)
!IS_WEB || !previousProps?.mainGroupSize || props.mainGroupSize === previousProps.mainGroupSize);
}), [mainGroupSize, mainGap, crossGap, numGroups, isVertical, itemHeights, itemWidths]);
const useGridLayout = useCallback(idxToKey => useDerivedValue(() => calculateLayout({
gaps: {
cross: crossGap.value,
main: mainGap.value
},
indexToKey: idxToKey.value,
isVertical,
itemHeights: itemHeights.value,
itemWidths: itemWidths.value,
mainGroupSize: mainGroupSize.value,
numGroups
})), [mainGroupSize, mainGap, crossGap, numGroups, isVertical, itemHeights, itemWidths]);
// GRID LAYOUT UPDATER
useGridLayoutReaction(indexToKey, (layout, shouldAnimate) => {
'worklet';
shouldAnimateLayout.value = shouldAnimate;
if (!layout || mainGroupSize.value === null) {
return;
}
// Update item positions
itemPositions.value = layout.itemPositions;
// Update controlled container dimensions
applyControlledContainerDimensions(layout.controlledContainerDimensions);
// DEBUG ONLY
if (debugCrossGapRects) {
for (let i = 0; i < layout.crossAxisOffsets.length - 1; i++) {
const size = crossGap.value;
const pos = layout.crossAxisOffsets[i + 1] - crossGap.value;
debugCrossGapRects[i]?.set({
...DEBUG_COLORS,
...(isVertical ? {
height: size,
y: pos
} : {
width: size,
x: pos
})
});
}
}
});
return {
value: {
crossGap,
isVertical,
mainGap,
mainGroupSize,
numGroups,
useGridLayout
}
};
});
export { GridLayoutProvider, useGridLayoutContext };
//# sourceMappingURL=GridLayoutProvider.js.map