react-native-sortables
Version:
Powerful Sortable Components for Flexible Content Reordering in React Native
108 lines (92 loc) • 3.29 kB
text/typescript
import { type ReactNode, useCallback } from 'react';
import type { View } from 'react-native';
import type { AnimatedRef, MeasuredDimensions } from 'react-native-reanimated';
import { measure, runOnUI } from 'react-native-reanimated';
import { HIDDEN_X_OFFSET } from '../../constants';
import {
useAnimatedDebounce,
useMutableValue
} from '../../integrations/reanimated';
import type { CustomHandleContextType, Vector } from '../../types';
import { createProvider } from '../utils';
import { useCommonValuesContext } from './CommonValuesProvider';
type CustomHandleProviderProps = {
children?: ReactNode;
};
const { CustomHandleProvider, useCustomHandleContext } = createProvider(
'CustomHandle',
{ guarded: false }
)<CustomHandleProviderProps, CustomHandleContextType>(() => {
const { containerRef, itemPositions } = useCommonValuesContext();
const debounce = useAnimatedDebounce();
const fixedItemKeys = useMutableValue<Record<string, boolean>>({});
const handleRefs = useMutableValue<Record<string, AnimatedRef<View>>>({});
const activeHandleMeasurements = useMutableValue<MeasuredDimensions | null>(
null
);
const activeHandleOffset = useMutableValue<null | Vector>(null);
const registerHandle = useCallback(
(key: string, handleRef: AnimatedRef<View>, fixed: boolean) => {
runOnUI(() => {
'worklet';
handleRefs.value[key] = handleRef;
if (fixed) {
fixedItemKeys.value[key] = true;
debounce.schedule(fixedItemKeys.modify, 100);
}
})();
const unregister = () => {
'worklet';
delete handleRefs.value[key];
if (fixed) {
fixedItemKeys.value[key] = false;
debounce.schedule(fixedItemKeys.modify, 100);
}
};
return runOnUI(unregister);
},
[debounce, fixedItemKeys, handleRefs]
);
const updateActiveHandleMeasurements = useCallback(
(key: string) => {
'worklet';
const handleRef = handleRefs.value[key];
if (!handleRef) {
return;
}
const handleMeasurements = measure(handleRef);
const containerMeasurements = measure(containerRef);
const itemPosition = itemPositions.value[key];
if (!handleMeasurements || !containerMeasurements || !itemPosition) {
return;
}
activeHandleMeasurements.value = handleMeasurements;
const { x: itemX, y: itemY } = itemPosition;
const { pageX: handleX, pageY: handleY } = handleMeasurements;
const { pageX: containerX, pageY: containerY } = containerMeasurements;
activeHandleOffset.value = {
// The handle might be measured when the item is already hidden (put outside
// of the viewport) so we need to adjust the measured x position accordingly
x: ((handleX + HIDDEN_X_OFFSET) % HIDDEN_X_OFFSET) - containerX - itemX,
y: handleY - containerY - itemY
};
},
[
activeHandleMeasurements,
activeHandleOffset,
containerRef,
handleRefs,
itemPositions
]
);
return {
value: {
activeHandleMeasurements,
activeHandleOffset,
fixedItemKeys,
registerHandle,
updateActiveHandleMeasurements
}
};
});
export { CustomHandleProvider, useCustomHandleContext };