sacmii / rn-vertical-slider

React Native Vertical Slider
MIT License
213 stars 46 forks source link

onComplete never gets triggered on web #91

Open phantom-factotum opened 6 months ago

phantom-factotum commented 6 months ago

No matter what I do onComplete is never called when the gesture ends:

import { Text, SafeAreaView, StyleSheet } from 'react-native';
import {useState} from 'react'
import VerticalSlider from 'rn-vertical-slider';

export default function App() {
  const [text,setText] = useState('not started')
  return (
    <SafeAreaView style={styles.container}>
      <Text>{text}</Text>
      <VerticalSlider
        min={0}
        max={1}
        step={0.05}
        value={0}
        width={20}
        height={100}
        onChange={()=>{
          setText('dragging')
        }}
        onComplete={() => {
          console.log('i am never called');
          setText('done')
        }}
      />
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems:'center',
    backgroundColor: '#ecf0f1',
    padding: 8,
  },
});

https://snack.expo.dev/g8-IGyiOx73T5kzMxqjNJ?platform=web

phantom-factotum commented 5 months ago

I took a look at the source code and useAnimatedGestureHandler is deprecated. While you could use useEvent and useHandler to recreate useAnimatedGestureHandler; using Gesure.Pan in combination with GestureDetector is easier:

import * as React from 'react';
import { StyleSheet, ViewStyle } from 'react-native';
import Animated, {
  useAnimatedStyle,
  withSpring,
  withTiming,
  runOnJS,
  useSharedValue,
} from 'react-native-reanimated';
import {
  GestureDetector,
  Gesture,
  GestureHandlerRootView,
} from 'react-native-gesture-handler';
import type { SliderProps } from './slider.types';

const calculateValue = (
  position: number,
  min: number,
  max: number,
  step: number,
  height: number
): number => {
  'worklet';
  let sliderPosition = height - position;
  sliderPosition = Math.min(Math.max(sliderPosition, 0), height);
  let value = (sliderPosition / height) * (max - min) + min;
  value = Math.round(value / step) * step;
  value = Math.min(Math.max(value, min), max);
  return value;
};

const VerticalSlider: React.FC<SliderProps> = ({
  min = 0,
  max = 100,
  step = 1,
  width = 350,
  height = 30,
  borderRadius = 5,
  maximumTrackTintColor = '#3F2DA5',
  minimumTrackTintColor = '#77ADE6',
  disabled = false,
  onChange = () => {},
  onComplete = () => {},
  value: currentValue = 0,
  showIndicator = false,
  renderIndicatorHeight = 40,
  renderIndicator = () => null,
  containerStyle = {},
  sliderStyle = {},
}: SliderProps) => {
  let point = useSharedValue<number>(currentValue);

  const gesture = Gesture.Pan()
    .onStart((evt) => {
      if (disabled) return;
      let value = calculateValue(evt.y, min, max, step, height);
      point.value = withSpring(value, {
        damping: 13,
      });
    })
    .onChange((evt) => {
      if (disabled) return;
      let value = calculateValue(evt.y, min, max, step, height);
      point.value = withTiming(value, { duration: 50 });
      runOnJS(onChange)(value);
    })
    .onEnd((evt) => {
      if (disabled) return;
      runOnJS(onComplete)(calculateValue(evt.y, min, max, step, height));
    });
  // All the dynamic style calculations
  const baseViewStyle = useAnimatedStyle<ViewStyle>(
    () => ({
      width,
      height,
      borderRadius,
      backgroundColor: maximumTrackTintColor,
    }),
    [point.value, maximumTrackTintColor, width, height, borderRadius]
  );
  // slider style
  const sliderBaseStyle = useAnimatedStyle<ViewStyle>(
    () => ({
      // Convert the value to height in number
      height: `${(point.value / max) * 100}%`,
      backgroundColor: minimumTrackTintColor,
      borderBottomLeftRadius: borderRadius,
      borderBottomRightRadius: borderRadius,
      // if percentage > 97 , then show border top right anf left radius
      ...((point.value / max) * 100 > 97
        ? {
            borderTopLeftRadius: borderRadius,
            borderTopRightRadius: borderRadius,
          }
        : {
            borderTopLeftRadius: 0,
            borderTopRightRadius: 0,
          }),
    }),
    [point.value, minimumTrackTintColor, borderRadius, height, max]
  );
  // indicator style
  const indicatorStyle = useAnimatedStyle<ViewStyle>(() => {
    if (!showIndicator) return {};
    let bottom = (point.value / max) * height;
    // Adjust the bottom value not to go beyond the height or less than 0
    bottom = Math.min(Math.max(bottom, 0), height - renderIndicatorHeight);
    return {
      bottom,
    };
  }, [showIndicator, point.value, sliderBaseStyle]);
  return (
    <GestureHandlerRootView>
      <GestureDetector gesture={gesture}>
        <Animated.View style={[baseViewStyle, containerStyle]}>
          <Animated.View
            style={[styles.slider, sliderBaseStyle, sliderStyle]}
          />
          <Animated.View style={[styles.slider, indicatorStyle]}>
            {renderIndicator(point.value)}
          </Animated.View>
        </Animated.View>
      </GestureDetector>
    </GestureHandlerRootView>
  );
};

const styles = StyleSheet.create({
  slider: {
    position: 'absolute',
    bottom: 0,
    borderRadius: 0,
    width: '100%',
  },
});

export default VerticalSlider;

https://snack.expo.dev/-gOkDbwYiqB3yzvIBzTxF