fix: make bar length actually update during drag with visual feedback

Fixed the draggable bars to show immediate visual feedback!

Problem: Bars didn't resize during drag because:
- Switching source unit kept relative proportions the same (log scale)
- Bar width was using item.percentage which didn't update visually
- No direct visual feedback for the dragged bar

Solution: Use draggedPercentage state for immediate visual updates
- Added draggedPercentage state to track visual position during drag
- Save baseConversionsRef when drag starts (preserves original scale)
- Calculate new value from percentage using BASE scale (not updated scale)
- Use displayPercentage = isDragging ? draggedPercentage : item.percentage
- Bar width and percentage label both use displayPercentage

How it works now:
1. Mouse/touch down: save base conversions and current percentage
2. Mouse/touch move: calculate new percentage from drag delta
3. Set draggedPercentage state immediately (visual update!)
4. Calculate value from percentage using BASE scale
5. Call onValueChange to update conversions
6. Dragged bar shows draggedPercentage, others show calculated percentage
7. On release: clear draggedPercentage, bars settle to calculated positions

Result: The dragged bar now visually follows your cursor in real-time!

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-08 10:56:46 +01:00
parent f214ddf0ba
commit a6a6f84618

View File

@@ -16,10 +16,12 @@ export default function VisualComparison({
onValueChange, onValueChange,
}: VisualComparisonProps) { }: VisualComparisonProps) {
const [draggingUnit, setDraggingUnit] = useState<string | null>(null); const [draggingUnit, setDraggingUnit] = useState<string | null>(null);
const [draggedPercentage, setDraggedPercentage] = useState<number | null>(null);
const dragStartX = useRef<number>(0); const dragStartX = useRef<number>(0);
const dragStartWidth = useRef<number>(0); const dragStartWidth = useRef<number>(0);
const activeBarRef = useRef<HTMLDivElement | null>(null); const activeBarRef = useRef<HTMLDivElement | null>(null);
const lastUpdateTime = useRef<number>(0); const lastUpdateTime = useRef<number>(0);
const baseConversionsRef = useRef<ConversionResult[]>([]);
// Calculate percentages for visual bars using logarithmic scale // Calculate percentages for visual bars using logarithmic scale
const withPercentages = useMemo(() => { const withPercentages = useMemo(() => {
if (conversions.length === 0) return []; if (conversions.length === 0) return [];
@@ -86,10 +88,13 @@ export default function VisualComparison({
e.preventDefault(); e.preventDefault();
setDraggingUnit(unit); setDraggingUnit(unit);
setDraggedPercentage(currentPercentage);
dragStartX.current = e.clientX; dragStartX.current = e.clientX;
dragStartWidth.current = currentPercentage; dragStartWidth.current = currentPercentage;
activeBarRef.current = barElement; activeBarRef.current = barElement;
}, [onValueChange]); // Save the current conversions as reference
baseConversionsRef.current = [...conversions];
}, [onValueChange, conversions]);
const handleMouseMove = useCallback((e: MouseEvent) => { const handleMouseMove = useCallback((e: MouseEvent) => {
if (!draggingUnit || !activeBarRef.current || !onValueChange) return; if (!draggingUnit || !activeBarRef.current || !onValueChange) return;
@@ -106,12 +111,14 @@ export default function VisualComparison({
let newPercentage = dragStartWidth.current + deltaPercentage; let newPercentage = dragStartWidth.current + deltaPercentage;
newPercentage = Math.max(3, Math.min(100, newPercentage)); newPercentage = Math.max(3, Math.min(100, newPercentage));
// Find the conversion result for this unit // Update visual percentage immediately
const conversion = conversions.find(c => c.unit === draggingUnit); setDraggedPercentage(newPercentage);
if (!conversion) return;
// Calculate min/max values for the scale // Use the base conversions (from when drag started) for scale calculation
const values = conversions.map(c => Math.abs(c.value)); const baseConversions = baseConversionsRef.current.length > 0 ? baseConversionsRef.current : conversions;
// Calculate min/max values for the scale from BASE conversions
const values = baseConversions.map(c => Math.abs(c.value));
const maxValue = Math.max(...values); const maxValue = Math.max(...values);
const minValue = Math.min(...values.filter(v => v > 0)); const minValue = Math.min(...values.filter(v => v > 0));
@@ -130,7 +137,9 @@ export default function VisualComparison({
} }
} }
setDraggingUnit(null); setDraggingUnit(null);
setDraggedPercentage(null);
activeBarRef.current = null; activeBarRef.current = null;
baseConversionsRef.current = [];
}, [draggingUnit, conversions, onValueChange]); }, [draggingUnit, conversions, onValueChange]);
// Touch drag handlers // Touch drag handlers
@@ -139,10 +148,13 @@ export default function VisualComparison({
const touch = e.touches[0]; const touch = e.touches[0];
setDraggingUnit(unit); setDraggingUnit(unit);
setDraggedPercentage(currentPercentage);
dragStartX.current = touch.clientX; dragStartX.current = touch.clientX;
dragStartWidth.current = currentPercentage; dragStartWidth.current = currentPercentage;
activeBarRef.current = barElement; activeBarRef.current = barElement;
}, [onValueChange]); // Save the current conversions as reference
baseConversionsRef.current = [...conversions];
}, [onValueChange, conversions]);
const handleTouchMove = useCallback((e: TouchEvent) => { const handleTouchMove = useCallback((e: TouchEvent) => {
if (!draggingUnit || !activeBarRef.current || !onValueChange) return; if (!draggingUnit || !activeBarRef.current || !onValueChange) return;
@@ -161,10 +173,13 @@ export default function VisualComparison({
let newPercentage = dragStartWidth.current + deltaPercentage; let newPercentage = dragStartWidth.current + deltaPercentage;
newPercentage = Math.max(3, Math.min(100, newPercentage)); newPercentage = Math.max(3, Math.min(100, newPercentage));
const conversion = conversions.find(c => c.unit === draggingUnit); // Update visual percentage immediately
if (!conversion) return; setDraggedPercentage(newPercentage);
const values = conversions.map(c => Math.abs(c.value)); // Use the base conversions (from when drag started) for scale calculation
const baseConversions = baseConversionsRef.current.length > 0 ? baseConversionsRef.current : conversions;
const values = baseConversions.map(c => Math.abs(c.value));
const maxValue = Math.max(...values); const maxValue = Math.max(...values);
const minValue = Math.min(...values.filter(v => v > 0)); const minValue = Math.min(...values.filter(v => v > 0));
@@ -182,7 +197,9 @@ export default function VisualComparison({
} }
} }
setDraggingUnit(null); setDraggingUnit(null);
setDraggedPercentage(null);
activeBarRef.current = null; activeBarRef.current = null;
baseConversionsRef.current = [];
}, [draggingUnit, conversions, onValueChange]); }, [draggingUnit, conversions, onValueChange]);
// Add/remove global event listeners for drag // Add/remove global event listeners for drag
@@ -215,6 +232,8 @@ export default function VisualComparison({
{withPercentages.map(item => { {withPercentages.map(item => {
const isDragging = draggingUnit === item.unit; const isDragging = draggingUnit === item.unit;
const isDraggable = !!onValueChange; const isDraggable = !!onValueChange;
// Use draggedPercentage if this bar is being dragged
const displayPercentage = isDragging && draggedPercentage !== null ? draggedPercentage : item.percentage;
return ( return (
<div key={item.unit} className="space-y-1.5"> <div key={item.unit} className="space-y-1.5">
@@ -255,14 +274,14 @@ export default function VisualComparison({
draggingUnit ? "transition-none" : "transition-all duration-500 ease-out" draggingUnit ? "transition-none" : "transition-all duration-500 ease-out"
)} )}
style={{ style={{
width: `${item.percentage}%`, width: `${displayPercentage}%`,
backgroundColor: color, backgroundColor: color,
}} }}
/> />
{/* Percentage label overlay */} {/* Percentage label overlay */}
<div className="absolute inset-0 flex items-center px-3 text-xs font-bold pointer-events-none"> <div className="absolute inset-0 flex items-center px-3 text-xs font-bold pointer-events-none">
<span className="text-foreground drop-shadow-sm"> <span className="text-foreground drop-shadow-sm">
{Math.round(item.percentage)}% {Math.round(displayPercentage)}%
</span> </span>
</div> </div>