aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
286 lines (283 loc) • 8.14 kB
JavaScript
import { useRef, useState, useCallback, useEffect } from 'react';
import { createVector2D } from '../../types/common.js';
/**
* Gesture Physics System
* Provides physics-based gesture interactions with preset configurations
*/
/**
* Gesture types supported by the physics system
*/
var GestureType;
(function (GestureType) {
GestureType["TAP"] = "tap";
GestureType["DOUBLE_TAP"] = "double_tap";
GestureType["LONG_PRESS"] = "long_press";
GestureType["PAN"] = "pan";
GestureType["SWIPE"] = "swipe";
GestureType["PINCH"] = "pinch";
GestureType["ROTATE"] = "rotate";
GestureType["HOVER"] = "hover";
})(GestureType || (GestureType = {}));
/**
* Predefined gesture physics presets
*/
const GESTURE_PRESETS = {
smooth: {
name: 'smooth',
damping: 26,
stiffness: 170,
mass: 1,
velocity: 0,
restDelta: 0.01,
restSpeed: 0.01,
gestures: [GestureType.PAN, GestureType.HOVER]
},
snappy: {
name: 'snappy',
damping: 20,
stiffness: 300,
mass: 0.5,
velocity: 0,
restDelta: 0.001,
restSpeed: 0.001,
gestures: [GestureType.TAP, GestureType.DOUBLE_TAP]
},
bouncy: {
name: 'bouncy',
damping: 15,
stiffness: 400,
mass: 1.5,
velocity: 0,
restDelta: 0.05,
restSpeed: 0.05,
gestures: [GestureType.SWIPE, GestureType.PAN]
},
gentle: {
name: 'gentle',
damping: 30,
stiffness: 120,
mass: 1.2,
velocity: 0,
restDelta: 0.01,
restSpeed: 0.01,
gestures: [GestureType.HOVER, GestureType.LONG_PRESS]
},
precise: {
name: 'precise',
damping: 40,
stiffness: 500,
mass: 0.8,
velocity: 0,
restDelta: 0.0001,
restSpeed: 0.0001,
gestures: [GestureType.PINCH, GestureType.ROTATE]
}
};
/**
* Hook for gesture-based physics interactions
*/
const useGesturePhysics = (options = {}) => {
const {
preset = 'smooth',
enabledGestures = Object.values(GestureType),
onGesture,
onGestureStart,
onGestureEnd,
threshold = 10,
velocityThreshold = 100,
longPressDelay = 500
} = options;
const gestureState = useRef({
active: false,
gestureType: null,
startPosition: createVector2D(0, 0),
currentPosition: createVector2D(0, 0),
velocity: createVector2D(0, 0),
startTime: 0,
lastTime: 0
});
const longPressTimer = useRef(null);
const tapTimer = useRef(null);
const lastTapTime = useRef(0);
const [position, setPosition] = useState(createVector2D(0, 0));
const [isGestureActive, setIsGestureActive] = useState(false);
// Get preset configuration
const presetConfig = typeof preset === 'string' ? GESTURE_PRESETS[preset] : preset;
/**
* Create gesture event
*/
const createGestureEvent = type => {
const state = gestureState.current;
const delta = {
x: state.currentPosition.x - state.startPosition.x,
y: state.currentPosition.y - state.startPosition.y
};
const distance = Math.sqrt(delta.x * delta.x + delta.y * delta.y);
const angle = Math.atan2(delta.y, delta.x);
return {
type,
position: state.currentPosition,
delta,
velocity: state.velocity,
distance,
angle,
scale: 1,
timestamp: Date.now()
};
};
/**
* Handle gesture start
*/
const handleGestureStart = useCallback((clientX, clientY) => {
const state = gestureState.current;
const currentTime = Date.now();
state.active = true;
state.startPosition = createVector2D(clientX, clientY);
state.currentPosition = createVector2D(clientX, clientY);
state.velocity = createVector2D(0, 0);
state.startTime = currentTime;
state.lastTime = currentTime;
setIsGestureActive(true);
// Check for double tap
if (enabledGestures.includes(GestureType.DOUBLE_TAP)) {
if (currentTime - lastTapTime.current < 300) {
state.gestureType = GestureType.DOUBLE_TAP;
const event = createGestureEvent(GestureType.DOUBLE_TAP);
onGestureStart?.(event);
onGesture?.(event);
lastTapTime.current = 0;
return;
}
lastTapTime.current = currentTime;
}
// Start long press timer
if (enabledGestures.includes(GestureType.LONG_PRESS)) {
longPressTimer.current = setTimeout(() => {
if (state.active) {
state.gestureType = GestureType.LONG_PRESS;
const event = createGestureEvent(GestureType.LONG_PRESS);
onGestureStart?.(event);
onGesture?.(event);
}
}, longPressDelay);
}
state.gestureType = GestureType.TAP;
onGestureStart?.(createGestureEvent(GestureType.TAP));
}, [enabledGestures, onGestureStart, onGesture, longPressDelay]);
/**
* Handle gesture move
*/
const handleGestureMove = useCallback((clientX, clientY) => {
const state = gestureState.current;
if (!state.active) return;
const currentTime = Date.now();
const dt = (currentTime - state.lastTime) / 1000;
const newPosition = createVector2D(clientX, clientY);
const delta = {
x: newPosition.x - state.currentPosition.x,
y: newPosition.y - state.currentPosition.y
};
// Calculate velocity
state.velocity = {
x: dt > 0 ? delta.x / dt : 0,
y: dt > 0 ? delta.y / dt : 0
};
state.currentPosition = newPosition;
state.lastTime = currentTime;
// Determine gesture type
const totalDistance = Math.sqrt(Math.pow(newPosition.x - state.startPosition.x, 2) + Math.pow(newPosition.y - state.startPosition.y, 2));
if (totalDistance > threshold) {
// Clear long press timer
if (longPressTimer.current) {
clearTimeout(longPressTimer.current);
longPressTimer.current = null;
}
const velocityMagnitude = Math.sqrt(state.velocity.x * state.velocity.x + state.velocity.y * state.velocity.y);
if (velocityMagnitude > velocityThreshold && enabledGestures.includes(GestureType.SWIPE)) {
state.gestureType = GestureType.SWIPE;
} else if (enabledGestures.includes(GestureType.PAN)) {
state.gestureType = GestureType.PAN;
}
}
setPosition(newPosition);
if (state.gestureType) {
onGesture?.(createGestureEvent(state.gestureType));
}
}, [enabledGestures, onGesture, threshold, velocityThreshold]);
/**
* Handle gesture end
*/
const handleGestureEnd = useCallback(() => {
const state = gestureState.current;
if (!state.active) return;
// Clear timers
if (longPressTimer.current) {
clearTimeout(longPressTimer.current);
longPressTimer.current = null;
}
if (state.gestureType) {
onGestureEnd?.(createGestureEvent(state.gestureType));
}
state.active = false;
state.gestureType = null;
setIsGestureActive(false);
}, [onGestureEnd]);
/**
* Handlers for different input methods
*/
const handlers = {
// Mouse events
onMouseDown: e => {
handleGestureStart(e.clientX, e.clientY);
},
onMouseMove: e => {
handleGestureMove(e.clientX, e.clientY);
},
onMouseUp: () => {
handleGestureEnd();
},
onMouseLeave: () => {
handleGestureEnd();
},
// Touch events
onTouchStart: e => {
const touch = e.touches[0];
if (touch) {
handleGestureStart(touch.clientX, touch.clientY);
}
},
onTouchMove: e => {
const touch = e.touches[0];
if (touch) {
handleGestureMove(touch.clientX, touch.clientY);
}
},
onTouchEnd: () => {
handleGestureEnd();
},
onTouchCancel: () => {
handleGestureEnd();
}
};
// Cleanup on unmount
useEffect(() => {
return () => {
if (longPressTimer.current) {
clearTimeout(longPressTimer.current);
}
if (tapTimer.current) {
clearTimeout(tapTimer.current);
}
};
}, []);
return {
handlers,
position,
isActive: isGestureActive,
gestureType: gestureState.current.gestureType,
velocity: gestureState.current.velocity,
preset: presetConfig
};
};
export { GESTURE_PRESETS, GestureType, useGesturePhysics };
//# sourceMappingURL=gesturePhysics.js.map