UNPKG

@chauffleet/expo-custom-map

Version:

Open source custom map library for Expo/React Native. Use your own tiles without Google Maps, Mapbox, or API keys. Created by ChaufFleet.

146 lines 6.76 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useFluidGestures = void 0; // src/hooks/useFluidGestures.ts - Hook pour gestes ultra-fluides const react_1 = require("react"); const react_native_1 = require("react-native"); const { width: screenWidth, height: screenHeight } = react_native_1.Dimensions.get('window'); const useFluidGestures = (config = {}) => { const { onPanUpdate, onPanEnd, onZoomUpdate, onZoomEnd, enableInertia = true, inertiaDecelerationRate = 0.95, maxInertiaVelocity = 3000, zoomSensitivity = 1, panThreshold = 2, } = config; // États des gestes const gestureState = (0, react_1.useRef)({ isPanning: false, isZooming: false, initialDistance: 0, initialCenter: { x: 0, y: 0 }, lastTimestamp: 0, velocityTracker: { x: 0, y: 0 }, }); // Animation refs const panAnim = (0, react_1.useRef)(new react_native_1.Animated.ValueXY()).current; const scaleAnim = (0, react_1.useRef)(new react_native_1.Animated.Value(1)).current; // Calculer la distance entre deux touches const calculateDistance = (0, react_1.useCallback)((touches) => { if (touches.length < 2) return 0; const dx = touches[0].pageX - touches[1].pageX; const dy = touches[0].pageY - touches[1].pageY; return Math.sqrt(dx * dx + dy * dy); }, []); // Calculer le centre entre deux touches const calculateCenter = (0, react_1.useCallback)((touches) => { if (touches.length < 2) return { x: 0, y: 0 }; return { x: (touches[0].pageX + touches[1].pageX) / 2, y: (touches[0].pageY + touches[1].pageY) / 2, }; }, []); // Mettre à jour le tracker de vélocité const updateVelocityTracker = (0, react_1.useCallback)((dx, dy) => { const now = Date.now(); const timeDelta = now - gestureState.current.lastTimestamp; if (timeDelta > 0) { gestureState.current.velocityTracker = { x: dx / timeDelta, y: dy / timeDelta, }; } gestureState.current.lastTimestamp = now; }, []); // Appliquer l'inertie après le geste const applyInertia = (0, react_1.useCallback)((velocityX, velocityY) => { if (!enableInertia) return; const velocity = Math.sqrt(velocityX * velocityX + velocityY * velocityY); if (velocity > 50 && velocity < maxInertiaVelocity) { const duration = Math.min(velocity * 2, 1500); react_native_1.Animated.decay(panAnim, { velocity: { x: velocityX * 1000, y: velocityY * 1000 }, deceleration: inertiaDecelerationRate, useNativeDriver: false, }).start(); } }, [enableInertia, maxInertiaVelocity, inertiaDecelerationRate, panAnim]); // PanResponder optimisé const panResponder = (0, react_1.useMemo)(() => react_native_1.PanResponder.create({ onMoveShouldSetPanResponder: (evt, gestureState) => { const { dx, dy } = gestureState; return Math.abs(dx) > panThreshold || Math.abs(dy) > panThreshold; }, onMoveShouldSetPanResponderCapture: () => false, onPanResponderTerminationRequest: () => false, onPanResponderGrant: (evt) => { // Arrêter toute animation en cours panAnim.stopAnimation(); scaleAnim.stopAnimation(); // Initialiser l'état du geste const touches = evt.nativeEvent.touches; gestureState.current = { isPanning: touches.length === 1, isZooming: touches.length === 2, initialDistance: calculateDistance(touches), initialCenter: calculateCenter(touches), lastTimestamp: Date.now(), velocityTracker: { x: 0, y: 0 }, }; }, onPanResponderMove: (evt, gestureStateParam) => { const touches = evt.nativeEvent.touches; const { dx, dy, numberActiveTouches } = gestureStateParam; // Mettre à jour le tracker de vélocité updateVelocityTracker(dx, dy); if (numberActiveTouches === 1 && gestureState.current.isPanning) { // Geste de pan onPanUpdate?.(dx, dy); } else if (numberActiveTouches === 2 && gestureState.current.isZooming) { // Geste de zoom const currentDistance = calculateDistance(touches); const currentCenter = calculateCenter(touches); if (gestureState.current.initialDistance > 0) { const scale = (currentDistance / gestureState.current.initialDistance) * zoomSensitivity; onZoomUpdate?.(scale, currentCenter.x, currentCenter.y); } } }, onPanResponderRelease: (evt, gestureStateParam) => { const { dx, dy, numberActiveTouches } = gestureStateParam; const { velocityTracker } = gestureState.current; if (gestureState.current.isPanning && numberActiveTouches === 0) { onPanEnd?.(dx, dy, velocityTracker.x, velocityTracker.y); // Appliquer l'inertie applyInertia(velocityTracker.x, velocityTracker.y); } else if (gestureState.current.isZooming) { const touches = evt.nativeEvent.touches; const currentDistance = calculateDistance(touches); const currentCenter = calculateCenter(touches); if (gestureState.current.initialDistance > 0) { const scale = (currentDistance / gestureState.current.initialDistance) * zoomSensitivity; onZoomEnd?.(scale, currentCenter.x, currentCenter.y); } } // Réinitialiser l'état gestureState.current.isPanning = false; gestureState.current.isZooming = false; }, onPanResponderTerminate: () => { // Nettoyer en cas d'interruption gestureState.current.isPanning = false; gestureState.current.isZooming = false; }, }), [ onPanUpdate, onPanEnd, onZoomUpdate, onZoomEnd, calculateDistance, calculateCenter, updateVelocityTracker, applyInertia, zoomSensitivity, panThreshold, panAnim, scaleAnim ]); return { panResponder, panAnim, scaleAnim, isGestureActive: gestureState.current.isPanning || gestureState.current.isZooming, }; }; exports.useFluidGestures = useFluidGestures; //# sourceMappingURL=useFluidGestures.js.map