react-native-sortables
Version:
Powerful Sortable Components for Flexible Content Reordering in React Native
177 lines (170 loc) • 5.61 kB
JavaScript
;
import { useCallback, useMemo } from 'react';
import { useAnimatedReaction, useDerivedValue } from 'react-native-reanimated';
import { IS_WEB } from '../../../constants';
import { useDebugContext } from '../../../debug';
import { useMutableValue } from '../../../integrations/reanimated';
import { haveEqualPropValues } from '../../../utils';
import { useAutoScrollContext, useCommonValuesContext, useItemsCount, useMeasurementsContext } from '../../shared';
import { createProvider } from '../../utils';
import { calculateLayout, updateLayoutDebugRects } from './utils';
const {
FlexLayoutProvider,
useFlexLayoutContext
} = createProvider('FlexLayout')(({
alignContent,
alignItems,
columnGap = 0,
flexDirection,
flexWrap,
height,
justifyContent,
maxHeight,
maxWidth,
minHeight,
minWidth,
padding,
paddingBottom,
paddingHorizontal,
paddingLeft,
paddingRight,
paddingTop,
paddingVertical,
rowGap = 0,
width
}) => {
const {
containerHeight,
containerWidth,
controlledContainerDimensions,
indexToKey,
itemHeights,
itemPositions,
itemWidths,
shouldAnimateLayout
} = useCommonValuesContext();
const {
applyControlledContainerDimensions
} = useMeasurementsContext();
const {
contentBounds
} = useAutoScrollContext() ?? {};
const debugContext = useDebugContext();
const keyToGroup = useMutableValue({});
const gaps = useMemo(() => ({
column: columnGap,
row: rowGap
}), [columnGap, rowGap]);
const paddings = useMemo(() => ({
bottom: paddingBottom ?? paddingVertical ?? padding,
left: paddingLeft ?? paddingHorizontal ?? padding,
right: paddingRight ?? paddingHorizontal ?? padding,
top: paddingTop ?? paddingVertical ?? padding
}), [paddingBottom, paddingVertical, padding, paddingLeft, paddingHorizontal, paddingRight, paddingTop]);
const dimensionsLimits = useDerivedValue(() => {
const h = height === 'fill' ? undefined : height;
const w = width === 'fill' ? undefined : width;
let minH = Math.max(minHeight ?? 0, h ?? 0);
let maxH = Math.min(maxHeight ?? Infinity, h ?? Infinity);
let minW = Math.max(minWidth ?? 0, w ?? 0);
let maxW = Math.min(maxWidth ?? Infinity, w ?? Infinity);
if (!controlledContainerDimensions.width) {
if (!containerWidth.value) {
return null;
}
minW = maxW = containerWidth.value;
}
if (!controlledContainerDimensions.height) {
if (!containerHeight.value) {
return null;
}
minH = maxH = containerHeight.value;
}
return {
maxHeight: maxH,
maxWidth: maxW,
minHeight: maxHeight ? Math.min(minH, maxH) : minH,
minWidth: maxWidth ? Math.min(minW, maxW) : minW
};
});
const appliedLayout = useMutableValue(null);
let debugCrossAxisGapRects;
let debugMainAxisGapRects;
if (__DEV__) {
// Because the number of groups can dynamically change after order change
// and we can't detect that in the React runtime, we are creating debug
// rects for the maximum number of groups that can be displayed (which
// is the number of items minus 1 in the worst case for just a single group)
const itemsCount = useItemsCount();
debugCrossAxisGapRects = debugContext?.useDebugRects(itemsCount - 1);
debugMainAxisGapRects = debugContext?.useDebugRects(itemsCount);
}
// FLEX LAYOUT UPDATER
useAnimatedReaction(() => ({
flexAlignments: {
alignContent,
alignItems,
justifyContent
},
flexDirection,
flexWrap,
gaps,
indexToKey: indexToKey.value,
itemHeights: itemHeights.value,
itemWidths: itemWidths.value,
limits: dimensionsLimits.value,
paddings
}), (props, previousProps) => {
const layout = calculateLayout(props);
// On web, animate layout only if parent container is not resized
// (e.g. skip animation when the browser window is resized)
shouldAnimateLayout.value = !IS_WEB || !previousProps?.limits || haveEqualPropValues(props?.limits, previousProps?.limits);
if (!layout) {
return;
}
// Update current layout
appliedLayout.value = layout;
// Update item positions
itemPositions.value = layout.itemPositions;
// Update controlled container dimensions
applyControlledContainerDimensions(layout.totalDimensions);
// Update key to group
keyToGroup.value = Object.fromEntries(layout.itemGroups.flatMap((group, i) => group.map(key => [key, i])));
// Update content bounds
if (contentBounds) contentBounds.value = layout.contentBounds;
// DEBUG ONLY
if (debugCrossAxisGapRects && debugMainAxisGapRects) {
updateLayoutDebugRects(flexDirection, layout, debugCrossAxisGapRects, debugMainAxisGapRects, itemWidths.value, itemHeights.value);
}
});
const calculateFlexLayout = useCallback(idxToKey => {
'worklet';
return calculateLayout({
flexAlignments: {
alignContent,
alignItems,
justifyContent
},
flexDirection,
flexWrap,
gaps,
indexToKey: idxToKey,
itemHeights: itemHeights.value,
itemWidths: itemWidths.value,
limits: dimensionsLimits.value,
paddings
});
}, [alignContent, alignItems, justifyContent, flexDirection, flexWrap, itemHeights, itemWidths, dimensionsLimits, paddings, gaps]);
return {
value: {
appliedLayout,
calculateFlexLayout,
columnGap,
flexDirection,
keyToGroup,
rowGap
}
};
});
export { FlexLayoutProvider, useFlexLayoutContext };
//# sourceMappingURL=FlexLayoutProvider.js.map