feat: add touch event support to knobs and faders

Added comprehensive touch event handling for mobile/tablet support:

CircularKnob.tsx:
- Added handleTouchStart, handleTouchMove, handleTouchEnd handlers
- Touch events use same drag logic as mouse events
- Prevents default to avoid scrolling while adjusting

TrackFader.tsx:
- Added touch event handlers for vertical fader control
- Integrated with existing onTouchStart/onTouchEnd callbacks
- Supports touch-based automation recording

MasterFader.tsx:
- Added touch event handlers matching TrackFader
- Complete touch support for master volume control

All components now work seamlessly on touch-enabled devices.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-19 01:19:03 +01:00
parent edebdc2129
commit 418c79d961
3 changed files with 99 additions and 4 deletions

View File

@@ -10,6 +10,8 @@ export interface MasterFaderProps {
isClipping: boolean;
onChange: (value: number) => void;
onResetClip?: () => void;
onTouchStart?: () => void;
onTouchEnd?: () => void;
className?: string;
}
@@ -20,6 +22,8 @@ export function MasterFader({
isClipping,
onChange,
onResetClip,
onTouchStart,
onTouchEnd,
className,
}: MasterFaderProps) {
const [isDragging, setIsDragging] = React.useState(false);
@@ -43,6 +47,7 @@ export function MasterFader({
const handleMouseDown = (e: React.MouseEvent) => {
e.preventDefault();
setIsDragging(true);
onTouchStart?.();
updateValue(e.clientY);
};
@@ -56,7 +61,30 @@ export function MasterFader({
const handleMouseUp = React.useCallback(() => {
setIsDragging(false);
}, []);
onTouchEnd?.();
}, [onTouchEnd]);
const handleTouchStart = (e: React.TouchEvent) => {
e.preventDefault();
const touch = e.touches[0];
setIsDragging(true);
onTouchStart?.();
updateValue(touch.clientY);
};
const handleTouchMove = React.useCallback(
(e: TouchEvent) => {
if (!isDragging || e.touches.length === 0) return;
const touch = e.touches[0];
updateValue(touch.clientY);
},
[isDragging]
);
const handleTouchEnd = React.useCallback(() => {
setIsDragging(false);
onTouchEnd?.();
}, [onTouchEnd]);
const updateValue = (clientY: number) => {
if (!containerRef.current) return;
@@ -72,12 +100,16 @@ export function MasterFader({
if (isDragging) {
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mouseup', handleMouseUp);
window.addEventListener('touchmove', handleTouchMove);
window.addEventListener('touchend', handleTouchEnd);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseup', handleMouseUp);
window.removeEventListener('touchmove', handleTouchMove);
window.removeEventListener('touchend', handleTouchEnd);
};
}
}, [isDragging, handleMouseMove, handleMouseUp]);
}, [isDragging, handleMouseMove, handleMouseUp, handleTouchMove, handleTouchEnd]);
return (
<div className={cn('flex gap-3', className)} style={{ marginLeft: '16px' }}>
@@ -94,6 +126,7 @@ export function MasterFader({
ref={containerRef}
className="relative w-12 h-40 bg-background/50 rounded-md border border-border/50 cursor-pointer"
onMouseDown={handleMouseDown}
onTouchStart={handleTouchStart}
>
{/* Peak Meter (Horizontal Bar - Top) */}
<div className="absolute inset-x-2 top-2 h-3 bg-background/80 rounded-sm overflow-hidden border border-border/30">