UNPKG

react-native-draggable-flatlist

Version:
289 lines (275 loc) 8.97 kB
import Animated, { clockRunning, not, startClock, stopClock, } from "react-native-reanimated"; import { isWeb } from "./constants"; const { set, cond, add, sub, block, eq, neq, and, divide, greaterThan, greaterOrEq, Value, spring, lessThan, lessOrEq, multiply, } = Animated; if (!Animated.proc) { throw new Error("Incompatible Reanimated version (proc not found)"); } // clock procs don't seem to work in web, not sure if there's a perf benefit to web procs anyway? const proc = isWeb ? <T>(cb: T) => cb : Animated.proc; export const getIsAfterActive = proc( (currentIndex: Animated.Node<number>, activeIndex: Animated.Node<number>) => greaterThan(currentIndex, activeIndex) ); export const hardReset = proc( ( position: Animated.Value<number>, finished: Animated.Value<number>, time: Animated.Value<number>, toValue: Animated.Value<number> ) => block([set(position, 0), set(finished, 0), set(time, 0), set(toValue, 0)]) ); /** * The in react-native-reanimated.d.ts definition of `proc` only has generics * for up to 10 arguments. We cast it to accept any params to avoid errors when * type-checking. */ type RetypedProc = (cb: (...params: any) => Animated.Node<number>) => typeof cb; export const setupCell = proc( ( currentIndex: Animated.Value<number>, size: Animated.Node<number>, offset: Animated.Node<number>, isAfterActive: Animated.Value<number>, prevToValue: Animated.Value<number>, prevSpacerIndex: Animated.Value<number>, activeIndex: Animated.Node<number>, activeCellSize: Animated.Node<number>, hoverOffset: Animated.Node<number>, spacerIndex: Animated.Value<number>, toValue: Animated.Value<number>, position: Animated.Value<number>, time: Animated.Value<number>, finished: Animated.Value<number>, runSpring: Animated.Node<number>, onFinished: Animated.Node<number>, isDraggingCell: Animated.Node<number>, placeholderOffset: Animated.Value<number>, prevIsDraggingCell: Animated.Value<number>, clock: Animated.Clock, disabled: Animated.Node<number> ) => block([ cond( greaterThan(activeIndex, -1), [ // Only update spacer if touch is not disabled. // Fixes android bugs where state would update with invalid touch values on touch end. cond(not(disabled), [ // Determine whether this cell is after the active cell in the list set(isAfterActive, getIsAfterActive(currentIndex, activeIndex)), // Determining spacer index is hard to visualize, see diagram: https://i.imgur.com/jRPf5t3.jpg cond( isAfterActive, [ cond( and( greaterOrEq(add(hoverOffset, activeCellSize), offset), lessThan( add(hoverOffset, activeCellSize), add(offset, divide(size, 2)) ) ), set(spacerIndex, sub(currentIndex, 1)) ), cond( and( greaterOrEq( add(hoverOffset, activeCellSize), add(offset, divide(size, 2)) ), lessThan( add(hoverOffset, activeCellSize), add(offset, size) ) ), set(spacerIndex, currentIndex) ), ], cond(lessThan(currentIndex, activeIndex), [ cond( and( lessThan(hoverOffset, add(offset, size)), greaterOrEq(hoverOffset, add(offset, divide(size, 2))) ), set(spacerIndex, add(currentIndex, 1)) ), cond( and( greaterOrEq(hoverOffset, offset), lessThan(hoverOffset, add(offset, divide(size, 2))) ), set(spacerIndex, currentIndex) ), ]) ), // Set placeholder offset cond(eq(spacerIndex, currentIndex), [ set( placeholderOffset, cond( isAfterActive, add(sub(offset, activeCellSize), size), offset ) ), ]), ]), cond( eq(currentIndex, activeIndex), [ // If this cell is the active cell cond( isDraggingCell, [ // Set its position to the drag position set(position, sub(hoverOffset, offset)), ], [ // Active item, not pressed in // Set value hovering element will snap to once released cond(prevIsDraggingCell, [ set(toValue, sub(placeholderOffset, offset)), // The clock starts automatically when toValue changes, however, we need to handle the // case where the item should snap back to its original location and toValue doesn't change cond(eq(prevToValue, toValue), [ cond(clockRunning(clock), stopClock(clock)), set(time, 0), set(finished, 0), startClock(clock), ]), ]), ] ), ], [ // Not the active item // Translate cell down if it is before active index and active cell has passed it. // Translate cell up if it is after the active index and active cell has passed it. set( toValue, cond( cond( isAfterActive, lessOrEq(currentIndex, spacerIndex), greaterOrEq(currentIndex, spacerIndex) ), cond( isAfterActive, multiply(activeCellSize, -1), activeCellSize ), 0 ) ), ] ), // If this cell should animate somewhere new, reset its state and start its clock cond(neq(toValue, prevToValue), [ cond(clockRunning(clock), stopClock(clock)), set(time, 0), set(finished, 0), startClock(clock), ]), cond(neq(prevSpacerIndex, spacerIndex), [ cond(eq(spacerIndex, -1), [ // Hard reset to prevent stale state bugs cond(clockRunning(clock), stopClock(clock)), hardReset(position, finished, time, toValue), ]), ]), cond(finished, [onFinished, set(time, 0), set(finished, 0)]), set(prevSpacerIndex, spacerIndex), set(prevToValue, toValue), set(prevIsDraggingCell, isDraggingCell), cond(clockRunning(clock), runSpring), ], [ // // Reset the spacer index when drag ends cond(neq(spacerIndex, -1), set(spacerIndex, -1)), cond(neq(position, 0), set(position, 0)), ] ), position, ]) ); const betterSpring = (proc as RetypedProc)( ( finished: Animated.Value<number>, velocity: Animated.Value<number>, position: Animated.Value<number>, time: Animated.Value<number>, prevPosition: Animated.Value<number>, toValue: Animated.Value<number>, damping: Animated.Value<number>, mass: Animated.Value<number>, stiffness: Animated.Value<number>, overshootClamping: Animated.SpringConfig["overshootClamping"], restSpeedThreshold: Animated.Value<number>, restDisplacementThreshold: Animated.Value<number>, clock: Animated.Clock ) => spring( clock, { finished, velocity, position, time, // @ts-ignore -- https://github.com/software-mansion/react-native-reanimated/blob/master/src/animations/spring.js#L177 prevPosition, }, { toValue, damping, mass, stiffness, overshootClamping, restDisplacementThreshold, restSpeedThreshold, } ) ); export function springFill( clock: Animated.Clock, state: Animated.SpringState, config: Animated.SpringConfig ) { return betterSpring( state.finished, state.velocity, state.position, state.time, new Value(0), config.toValue, config.damping, config.mass, config.stiffness, config.overshootClamping, config.restSpeedThreshold, config.restDisplacementThreshold, clock ); }