CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-native-redash

Utility library for React Native Reanimated and Gesture Handler providing mathematical functions, animations, transformations, and helper utilities for building complex gesture-driven animations.

Pending
Overview
Eval results
Files

utilities.mddocs/

Utility Functions

Additional utility functions for physics calculations and array manipulations that support common animation and interaction patterns.

Capabilities

Physics Utilities

Snap Point Selection

Calculate the optimal snap point based on gesture velocity and current position.

/**
 * Select snap point based on value and velocity
 * @param value - Current position/value
 * @param velocity - Current velocity
 * @param points - Array of possible snap points
 * @returns The optimal snap point
 */
function snapPoint(
  value: number,
  velocity: number,
  points: ReadonlyArray<number>
): number;

Usage Example:

import { snapPoint } from "react-native-redash";
import { 
  useSharedValue,
  useAnimatedGestureHandler,
  withSpring
} from "react-native-reanimated";
import { PanGestureHandler } from "react-native-gesture-handler";

export const SnapScrollView = () => {
  const translateX = useSharedValue(0);
  
  // Define snap points (e.g., for horizontal card stack)
  const snapPoints = [0, -150, -300, -450];
  
  const gestureHandler = useAnimatedGestureHandler({
    onStart: () => {
      // Store initial position if needed
    },
    onActive: (event) => {
      translateX.value = event.translationX;
    },
    onEnd: (event) => {
      // Find optimal snap point based on position and velocity
      const destination = snapPoint(
        translateX.value,
        event.velocityX,
        snapPoints
      );
      
      // Animate to snap point
      translateX.value = withSpring(destination, {
        damping: 15,
        stiffness: 100
      });
    }
  });
  
  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ translateX: translateX.value }]
  }));
  
  return (
    <PanGestureHandler onGestureEvent={gestureHandler}>
      <Animated.View style={[styles.scrollContainer, animatedStyle]}>
        {/* Scrollable content */}
      </Animated.View>
    </PanGestureHandler>
  );
};

Advanced Snap Point Example:

import { snapPoint } from "react-native-redash";

export const VerticalSnapCarousel = () => {
  const translateY = useSharedValue(0);
  const cardHeight = 200;
  const cardSpacing = 20;
  
  // Generate snap points for vertical card stack
  const snapPoints = Array.from({ length: 5 }, (_, i) => 
    -(i * (cardHeight + cardSpacing))
  );
  
  const gestureHandler = useAnimatedGestureHandler({
    onEnd: (event) => {
      const currentPosition = translateY.value;
      const velocity = event.velocityY;
      
      // Factor in velocity for more natural snapping
      const destination = snapPoint(currentPosition, velocity, snapPoints);
      
      translateY.value = withSpring(destination, {
        damping: 20,
        stiffness: 200,
        mass: 1
      });
    }
  });
  
  return (
    <PanGestureHandler onGestureEvent={gestureHandler}>
      <Animated.View style={animatedStyle}>
        {/* Cards */}
      </Animated.View>
    </PanGestureHandler>
  );
};

Array Utilities

Array Element Moving

Move elements within an array while maintaining proper indices.

/**
 * Move array element from one index to another
 * @param input - Input array to modify
 * @param from - Source index
 * @param to - Destination index
 * @returns New array with moved element
 */
function move<T>(input: T[], from: number, to: number): T[];

Usage Example:

import { move } from "react-native-redash";
import { useSharedValue, runOnJS } from "react-native-reanimated";

export const ReorderableList = () => {
  const [items, setItems] = useState(['A', 'B', 'C', 'D', 'E']);
  const draggedIndex = useSharedValue(-1);
  
  const moveItem = (fromIndex: number, toIndex: number) => {
    setItems(current => move(current, fromIndex, toIndex));
  };
  
  const gestureHandler = useAnimatedGestureHandler({
    onStart: (event, context) => {
      // Determine which item is being dragged
      const index = Math.floor(event.y / ITEM_HEIGHT);
      draggedIndex.value = index;
      context.startIndex = index;
    },
    onActive: (event, context) => {
      const currentIndex = Math.floor(event.y / ITEM_HEIGHT);
      
      if (currentIndex !== context.startIndex) {
        // Move item in array
        runOnJS(moveItem)(context.startIndex, currentIndex);
        context.startIndex = currentIndex;
      }
    },
    onEnd: () => {
      draggedIndex.value = -1;
    }
  });
  
  return (
    <PanGestureHandler onGestureEvent={gestureHandler}>
      <View>
        {items.map((item, index) => (
          <ReorderableItem 
            key={item} 
            item={item} 
            index={index}
            isDragged={draggedIndex.value === index}
          />
        ))}
      </View>
    </PanGestureHandler>
  );
};

Animated List Reordering:

import { move } from "react-native-redash";
import { 
  useSharedValue,
  useAnimatedStyle,
  withSpring,
  runOnJS
} from "react-native-reanimated";

export const AnimatedReorderableList = () => {
  const [data, setData] = useState([
    { id: '1', title: 'Item 1' },
    { id: '2', title: 'Item 2' },
    { id: '3', title: 'Item 3' },
    { id: '4', title: 'Item 4' }
  ]);
  
  const positions = useSharedValue(
    data.map((_, index) => index)
  );
  
  const reorderItems = (from: number, to: number) => {
    // Update both data and positions
    setData(current => move(current, from, to));
    positions.value = move(positions.value, from, to);
  };
  
  const createGestureHandler = (index: number) => 
    useAnimatedGestureHandler({
      onActive: (event) => {
        const newIndex = Math.floor(event.absoluteY / ITEM_HEIGHT);
        
        if (newIndex !== index && newIndex >= 0 && newIndex < data.length) {
          runOnJS(reorderItems)(index, newIndex);
        }
      }
    });
  
  return (
    <View>
      {data.map((item, index) => {
        const animatedStyle = useAnimatedStyle(() => ({
          transform: [
            { translateY: withSpring(positions.value[index] * ITEM_HEIGHT) }
          ]
        }));
        
        return (
          <PanGestureHandler 
            key={item.id}
            onGestureEvent={createGestureHandler(index)}
          >
            <Animated.View style={[styles.item, animatedStyle]}>
              <Text>{item.title}</Text>
            </Animated.View>
          </PanGestureHandler>
        );
      })}
    </View>
  );
};

Combined Physics and Array Example

import { snapPoint, move } from "react-native-redash";

export const SnapReorderCarousel = () => {
  const [items, setItems] = useState(['Card 1', 'Card 2', 'Card 3', 'Card 4']);
  const translateX = useSharedValue(0);
  const activeIndex = useSharedValue(0);
  
  const cardWidth = 250;
  const cardSpacing = 20;
  
  // Calculate snap points for each card
  const snapPoints = items.map((_, index) => 
    -(index * (cardWidth + cardSpacing))
  );
  
  const gestureHandler = useAnimatedGestureHandler({
    onActive: (event) => {
      translateX.value = event.translationX;
      
      // Calculate which card is currently in focus
      const currentIndex = Math.round(-translateX.value / (cardWidth + cardSpacing));
      activeIndex.value = Math.max(0, Math.min(items.length - 1, currentIndex));
    },
    onEnd: (event) => {
      // Snap to nearest card
      const destination = snapPoint(
        translateX.value,
        event.velocityX,
        snapPoints
      );
      
      translateX.value = withSpring(destination);
      
      // Update active index based on final position
      const finalIndex = Math.round(-destination / (cardWidth + cardSpacing));
      activeIndex.value = finalIndex;
    }
  });
  
  // Double tap to move card to front
  const doubleTapHandler = useAnimatedGestureHandler({
    onEnd: () => {
      const currentIndex = activeIndex.value;
      if (currentIndex > 0) {
        runOnJS(setItems)(current => move(current, currentIndex, 0));
        translateX.value = withSpring(0);
        activeIndex.value = 0;
      }
    }
  });
  
  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ translateX: translateX.value }]
  }));
  
  return (
    <PanGestureHandler onGestureEvent={gestureHandler}>
      <TapGestureHandler numberOfTaps={2} onGestureEvent={doubleTapHandler}>
        <Animated.View style={[styles.carousel, animatedStyle]}>
          {items.map((item, index) => (
            <View key={item} style={styles.card}>
              <Text>{item}</Text>
            </View>
          ))}
        </Animated.View>
      </TapGestureHandler>
    </PanGestureHandler>
  );
};

Utility Function Behavior:

  • snapPoint: Uses velocity and position to predict where the user intends to end up
  • move: Handles negative indices by wrapping around the array length
  • Both functions are optimized for use in gesture handlers and animations
  • snapPoint considers momentum (velocity * 0.2) when calculating the target destination
  • move creates a new array and handles edge cases like moving beyond array bounds

Install with Tessl CLI

npx tessl i tessl/npm-react-native-redash

docs

animations.md

colors.md

components.md

coordinates.md

index.md

math.md

matrices.md

paths.md

transforms.md

transitions.md

utilities.md

vectors.md

tile.json