Skip to content

Commit aca1eb2

Browse files
committed
refactor: simplify gesture handling in RangeSlider component
1 parent cc721d3 commit aca1eb2

File tree

2 files changed

+86
-109
lines changed

2 files changed

+86
-109
lines changed

README.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ A high-performance React Native range slider component built with [react-native-
44

55
## Features
66

7-
- 🚀 High performance using react-native-reanimated
8-
- 🎯 Precise touch controls with react-native-gesture-handler
9-
- 💨 Smooth animations running on UI thread
10-
- 🔄 Real-time value updates
11-
- 🎨 Fully customizable styling
12-
- ♿️ Accessibility support
13-
- 📱 Pure JavaScript implementation - no native code or linking needed
14-
- 🔧 Configurable min/max values and step sizes
15-
- 🎛 Support for minimum distance between thumbs
16-
- 🌐 RTL (Right-to-Left) support
17-
- ⚡️ Works with Expo out of the box
7+
- High performance using react-native-reanimated
8+
- Precise touch controls with react-native-gesture-handler
9+
- Smooth animations running on UI thread
10+
- Real-time value updates
11+
- Fully customizable styling
12+
- Accessibility support
13+
- Pure TypeScript implementation - no native code or linking needed
14+
- Configurable min/max values and step sizes
15+
- Support for minimum distance between thumbs
16+
- RTL (Right-to-Left) support
17+
- Works with Expo out of the box
1818

1919
## Performance Benefits
2020

src/RangeSlider.tsx

Lines changed: 75 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,15 @@ import {
77
type ForwardRefRenderFunction,
88
} from 'react';
99
import { View, StyleSheet, type ViewStyle } from 'react-native';
10-
import {
11-
PanGestureHandler,
12-
GestureHandlerRootView,
13-
type PanGestureHandlerGestureEvent,
14-
} from 'react-native-gesture-handler';
10+
import { GestureHandlerRootView } from 'react-native-gesture-handler';
1511
import Animated, {
1612
useAnimatedStyle,
1713
useSharedValue,
1814
useDerivedValue,
1915
runOnJS,
2016
type SharedValue,
21-
useAnimatedGestureHandler,
2217
} from 'react-native-reanimated';
18+
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
2319
import type { RangeSliderProps } from './types';
2420

2521
// ================ Constants ================
@@ -56,11 +52,6 @@ interface PressedState {
5652
right: boolean;
5753
}
5854

59-
interface GestureContext {
60-
startX: number;
61-
[key: string]: unknown;
62-
}
63-
6455
// ================ Styles ================
6556
const styles = StyleSheet.create({
6657
markerContainer: {
@@ -283,22 +274,19 @@ const RangeSlider: ForwardRefRenderFunction<View, RangeSliderProps> = (
283274
[onValuesChange]
284275
);
285276

286-
const leftGesture = useAnimatedGestureHandler<
287-
PanGestureHandlerGestureEvent,
288-
GestureContext
289-
>({
290-
onStart: (_: any, ctx: GestureContext) => {
277+
const leftGesture = Gesture.Pan()
278+
.hitSlop(TOUCH_HITSLOP)
279+
.onStart(() => {
291280
'worklet';
292281
if (!enabled) return;
293-
ctx.startX = leftPos.value;
294282
runOnJS(setPressed)({ left: true, right: false });
295283
runOnJS(onValuesChangeStart)(updateValues(leftPos.value, rightPos.value));
296-
},
297-
onActive: (event: any, ctx: GestureContext) => {
284+
})
285+
.onUpdate((event) => {
298286
'worklet';
299287
if (!enabled) return;
300288

301-
const position = ctx.startX + event.translationX;
289+
const position = leftOffset.value + event.translationX;
302290
const minPosition = HORIZONTAL_PADDING;
303291
const maxPosition = allowOverlap
304292
? rightPos.value
@@ -309,14 +297,12 @@ const RangeSlider: ForwardRefRenderFunction<View, RangeSliderProps> = (
309297
Math.min(maxPosition, position)
310298
);
311299

312-
// Update position directly without animation
313300
leftPos.value = clampedPosition;
314-
leftOffset.value = clampedPosition;
315301

316302
const newValues = updateValues(clampedPosition, rightPos.value);
317303
runOnJS(updateOutputValues)(newValues);
318-
},
319-
onEnd: () => {
304+
})
305+
.onEnd(() => {
320306
'worklet';
321307
if (!enabled) return;
322308

@@ -329,25 +315,21 @@ const RangeSlider: ForwardRefRenderFunction<View, RangeSliderProps> = (
329315

330316
runOnJS(onValuesChangeFinish)(values);
331317
runOnJS(setPressed)({ left: false, right: false });
332-
},
333-
});
318+
});
334319

335-
const rightGesture = useAnimatedGestureHandler<
336-
PanGestureHandlerGestureEvent,
337-
GestureContext
338-
>({
339-
onStart: (_: any, ctx: GestureContext) => {
320+
const rightGesture = Gesture.Pan()
321+
.hitSlop(TOUCH_HITSLOP)
322+
.onStart(() => {
340323
'worklet';
341324
if (!enabled) return;
342-
ctx.startX = rightPos.value;
343325
runOnJS(setPressed)({ left: false, right: true });
344326
runOnJS(onValuesChangeStart)(updateValues(leftPos.value, rightPos.value));
345-
},
346-
onActive: (event: any, ctx: GestureContext) => {
327+
})
328+
.onUpdate((event) => {
347329
'worklet';
348330
if (!enabled) return;
349331

350-
const position = ctx.startX + event.translationX;
332+
const position = rightOffset.value + event.translationX;
351333
const minPosition = allowOverlap
352334
? leftPos.value
353335
: leftPos.value + minimumDistance;
@@ -358,14 +340,12 @@ const RangeSlider: ForwardRefRenderFunction<View, RangeSliderProps> = (
358340
Math.min(maxPosition, position)
359341
);
360342

361-
// Update position directly without animation
362343
rightPos.value = clampedPosition;
363-
rightOffset.value = clampedPosition;
364344

365345
const newValues = updateValues(leftPos.value, clampedPosition);
366346
runOnJS(updateOutputValues)(newValues);
367-
},
368-
onEnd: () => {
347+
})
348+
.onEnd(() => {
369349
'worklet';
370350
if (!enabled) return;
371351

@@ -378,8 +358,7 @@ const RangeSlider: ForwardRefRenderFunction<View, RangeSliderProps> = (
378358

379359
runOnJS(onValuesChangeFinish)(values);
380360
runOnJS(setPressed)({ left: false, right: false });
381-
},
382-
});
361+
});
383362

384363
// Update positions only when initial values change externally
385364
useEffect(() => {
@@ -427,65 +406,63 @@ const RangeSlider: ForwardRefRenderFunction<View, RangeSliderProps> = (
427406
]}
428407
/>
429408

430-
<PanGestureHandler
431-
onGestureEvent={leftGesture}
432-
hitSlop={TOUCH_HITSLOP}
433-
minDist={0}
434-
activeOffsetX={[-10, 10]}
409+
<Animated.View
410+
style={[
411+
dynamicStyles.thumb,
412+
styles.markerContainer,
413+
thumbStyle,
414+
pressed.left && pressedThumbStyle,
415+
leftThumbStyle,
416+
]}
435417
>
436-
<Animated.View
437-
accessible={true}
438-
accessibilityLabel={leftThumbAccessibilityLabel}
439-
accessibilityRole="adjustable"
440-
style={[
441-
dynamicStyles.thumb,
442-
styles.markerContainer,
443-
thumbStyle,
444-
pressed.left && pressedThumbStyle,
445-
leftThumbStyle,
446-
]}
447-
>
448-
<View style={staticStyles.thumbInner}>
449-
{showThumbLines && (
450-
<>
451-
<View style={staticStyles.markerLine} />
452-
<View style={staticStyles.markerLine} />
453-
<View style={staticStyles.markerLine} />
454-
</>
455-
)}
456-
</View>
457-
</Animated.View>
458-
</PanGestureHandler>
459-
460-
<PanGestureHandler
461-
onGestureEvent={rightGesture}
462-
hitSlop={TOUCH_HITSLOP}
463-
minDist={0}
464-
activeOffsetX={[-10, 10]}
418+
<GestureDetector gesture={leftGesture}>
419+
<Animated.View
420+
accessible={true}
421+
accessibilityLabel={leftThumbAccessibilityLabel}
422+
accessibilityRole="adjustable"
423+
style={[{ width: '100%', height: '100%' }]}
424+
>
425+
<View style={staticStyles.thumbInner}>
426+
{showThumbLines && (
427+
<>
428+
<View style={staticStyles.markerLine} />
429+
<View style={staticStyles.markerLine} />
430+
<View style={staticStyles.markerLine} />
431+
</>
432+
)}
433+
</View>
434+
</Animated.View>
435+
</GestureDetector>
436+
</Animated.View>
437+
438+
<Animated.View
439+
style={[
440+
dynamicStyles.thumb,
441+
styles.markerContainer,
442+
thumbStyle,
443+
pressed.right && pressedThumbStyle,
444+
rightThumbStyle,
445+
]}
465446
>
466-
<Animated.View
467-
accessible={true}
468-
accessibilityLabel={rightThumbAccessibilityLabel}
469-
accessibilityRole="adjustable"
470-
style={[
471-
dynamicStyles.thumb,
472-
styles.markerContainer,
473-
thumbStyle,
474-
pressed.right && pressedThumbStyle,
475-
rightThumbStyle,
476-
]}
477-
>
478-
<View style={staticStyles.thumbInner}>
479-
{showThumbLines && (
480-
<>
481-
<View style={staticStyles.markerLine} />
482-
<View style={staticStyles.markerLine} />
483-
<View style={staticStyles.markerLine} />
484-
</>
485-
)}
486-
</View>
487-
</Animated.View>
488-
</PanGestureHandler>
447+
<GestureDetector gesture={rightGesture}>
448+
<Animated.View
449+
accessible={true}
450+
accessibilityLabel={rightThumbAccessibilityLabel}
451+
accessibilityRole="adjustable"
452+
style={[{ width: '100%', height: '100%' }]}
453+
>
454+
<View style={staticStyles.thumbInner}>
455+
{showThumbLines && (
456+
<>
457+
<View style={staticStyles.markerLine} />
458+
<View style={staticStyles.markerLine} />
459+
<View style={staticStyles.markerLine} />
460+
</>
461+
)}
462+
</View>
463+
</Animated.View>
464+
</GestureDetector>
465+
</Animated.View>
489466
</View>
490467
</GestureHandlerRootView>
491468
);

0 commit comments

Comments
 (0)