UNPKG

sonner-native

Version:

An opinionated toast component for React Native. A port of @emilkowalski's sonner.

174 lines (164 loc) 5.19 kB
"use strict"; import * as React from 'react'; import { Dimensions, Platform } from 'react-native'; import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import Animated, { Easing, LinearTransition, interpolate, runOnJS, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'; import { useToastContext } from "./context.js"; import { easeInOutCircFn } from "./easings.js"; import { jsx as _jsx } from "react/jsx-runtime"; const { width: WINDOW_WIDTH } = Dimensions.get('window'); export const ToastSwipeHandler = ({ children, onRemove, style, onBegin, onFinalize, enabled, unstyled, important, position: positionProps, onPress }) => { const translate = useSharedValue(0); const { swipeToDismissDirection: direction, gap, position: positionCtx } = useToastContext(); const position = positionProps || positionCtx; const pan = Gesture.Pan().onBegin(() => { 'worklet'; if (!enabled) { return; } runOnJS(onBegin)(); }).onChange(event => { 'worklet'; if (!enabled) { return; } if (direction === 'left' && event.translationX < 0) { translate.value = event.translationX; } else if (direction === 'up') { const isBottomPosition = position === 'bottom-center'; const rawTranslation = event.translationY * (isBottomPosition ? -1 : 1); // Define correct and wrong directions based on position const isCorrectDirection = rawTranslation < 0; // negative means correct dismissal direction const isWrongDirection = rawTranslation > 0; // positive means wrong direction if (isCorrectDirection) { // Allow full movement in correct direction translate.value = rawTranslation; } else if (isWrongDirection) { translate.value = elasticResistance(rawTranslation); } } }).onFinalize(() => { 'worklet'; if (!enabled) { return; } if (direction === 'left') { const threshold = -WINDOW_WIDTH * 0.25; const shouldDismiss = translate.value < threshold; if (Math.abs(translate.value) < 16) { translate.value = withTiming(0, { easing: Easing.elastic(0.8) }); return; } if (shouldDismiss) { translate.value = withTiming(-WINDOW_WIDTH, { easing: Easing.inOut(Easing.ease) }, isDone => { if (isDone) { runOnJS(onRemove)(); } }); } else { translate.value = withTiming(0, { easing: Easing.elastic(0.8) }); } } else if (direction === 'up') { const threshold = -16; const shouldDismiss = translate.value < threshold; const isWrongDirection = translate.value > 0; // If dragged in wrong direction, always spring back if (isWrongDirection) { translate.value = withTiming(0, { easing: Easing.elastic(0.8), duration: 400 }); } else if (Math.abs(translate.value) < 16) { translate.value = withTiming(0, { easing: Easing.elastic(0.8) }); } else if (shouldDismiss) { translate.value = withTiming(-WINDOW_WIDTH, { easing: Easing.inOut(Easing.ease) }, isDone => { if (isDone) { runOnJS(onRemove)(); } }); } else { translate.value = withTiming(0, { easing: Easing.elastic(0.8) }); } } runOnJS(onFinalize)(); }); const tap = Gesture.Tap().onEnd(() => { 'worklet'; if (onPress) { runOnJS(onPress)(); } }); const isAndroid = Platform.OS === 'android'; const animatedStyle = useAnimatedStyle(() => { const aStyle = { transform: [direction === 'left' ? { translateX: translate.value } : { translateY: translate.value * (position === 'bottom-center' ? -1 : 1) }] }; if (isAndroid) { aStyle.opacity = 1; } else { aStyle.opacity = interpolate(translate.value, [0, direction === 'left' ? -WINDOW_WIDTH : -60], [1, 0]); } return aStyle; }, [direction, translate]); return /*#__PURE__*/_jsx(GestureDetector, { gesture: Gesture.Race(tap, pan), children: /*#__PURE__*/_jsx(Animated.View, { style: [animatedStyle, unstyled ? undefined : { justifyContent: 'center', marginBottom: gap }, { width: '100%' }, Platform.OS === 'android' ? { opacity: 1 } : {}, style], layout: LinearTransition.easing(easeInOutCircFn), "aria-live": important ? 'assertive' : 'polite' // https://reactnative.dev/docs/accessibility#aria-live-android , children: children }) }); }; // Apply progressive elastic resistance (Apple-style) // This function provides diminishing returns as the drag distance increases function elasticResistance(distance) { 'worklet'; // Base resistance factor const baseResistance = 0.4; // Progressive dampening - the further you drag, the more resistance const progressiveFactor = 1 / (1 + distance * 0.02); return distance * baseResistance * progressiveFactor; } //# sourceMappingURL=gestures.js.map