@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
JavaScript
;
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