UNPKG

react-native-sortables

Version:

Powerful Sortable Components for Flexible Content Reordering in React Native

121 lines (103 loc) 3.37 kB
import { useMemo } from 'react'; import { type StyleProp, StyleSheet, type ViewStyle } from 'react-native'; import type { AnimatedStyle, SharedValue } from 'react-native-reanimated'; import { interpolate, measure, useAnimatedReaction, useAnimatedStyle, useSharedValue } from 'react-native-reanimated'; import type { Vector } from '../../../types'; import { useCommonValuesContext } from '../CommonValuesProvider'; import { usePortalOutletContext } from '../PortalOutletProvider'; import { usePortalContext } from '../PortalProvider'; import useItemZIndex from './useItemZIndex'; export default function useTeleportedItemStyles( key: string, isActive: SharedValue<boolean>, activationAnimationProgress: SharedValue<number> ): StyleProp<AnimatedStyle<ViewStyle>> { const { activeItemAbsolutePosition } = usePortalContext()!; const { portalOutletRef } = usePortalOutletContext()!; const { activeItemKey, containerRef, itemPositions } = useCommonValuesContext(); const zIndex = useItemZIndex(key, activationAnimationProgress); const dropStartTranslation = useSharedValue<null | Vector>(null); const absoluteX = useSharedValue<null | number>(null); const absoluteY = useSharedValue<null | number>(null); // Inactive item updater (for drop animation) useAnimatedReaction( () => ({ activationProgress: activationAnimationProgress.value, active: isActive.value, position: itemPositions.value[key] }), ({ activationProgress, active, position }) => { if ( active || !position || !activationProgress || absoluteX.value === null || absoluteY.value === null ) { dropStartTranslation.value = null; return; } const containerMeasurements = measure(containerRef); if (!containerMeasurements) { return; } // Drop animation dropStartTranslation.value ??= { x: absoluteX.value, y: absoluteY.value }; const animate = (from: number, to: number) => interpolate(activationProgress, [1, 0], [from, to]); const { x, y } = dropStartTranslation.value; absoluteX.value = animate(x, containerMeasurements.pageX + position.x); absoluteY.value = animate(y, containerMeasurements.pageY + position.y); } ); // Active item updater useAnimatedReaction( () => ({ active: activeItemKey.value === key, position: activeItemAbsolutePosition.value }), ({ active, position }) => { if (!active || !position) { return; } absoluteX.value = position.x; absoluteY.value = position.y; } ); const animatedStyle = useAnimatedStyle(() => { const portalOutletMeasurements = measure(portalOutletRef); if ( absoluteX.value === null || absoluteY.value === null || !portalOutletMeasurements ) { return { opacity: 0 }; } const dX = portalOutletMeasurements.pageX; const dY = portalOutletMeasurements.pageY; return { opacity: 1, transform: [ { translateX: absoluteX.value - dX }, { translateY: absoluteY.value - dY } ], zIndex: zIndex.value }; }); return useMemo(() => [styles.container, animatedStyle], [animatedStyle]); } const styles = StyleSheet.create({ container: { position: 'absolute' } });