import { useCallback, useEffect, useState } from "react" import { cn } from "@/lib/utils" import { useEditModeContext } from "../../hooks/use-edit-mode" import { EDIT_MODES } from "../../types/edit-modes" interface ClipTrimHandlesProps { onTrimStart: (edge: "start" | "end", initialX: number) => void onTrimMove: (deltaX: number) => void onTrimEnd: (committed: boolean) => void isSelected: boolean disabled?: boolean className?: string } export function ClipTrimHandles({ onTrimStart, onTrimMove, onTrimEnd, isSelected, disabled = false, className, }: ClipTrimHandlesProps) { const { isEditMode } = useEditModeContext() const [isDragging, setIsDragging] = useState(false) const [dragEdge, setDragEdge] = useState<"start" | "end" | null>(null) const [dragStartX, setDragStartX] = useState(0) // Show handles when in trim/ripple mode or when clip is selected const showHandles = (isEditMode(EDIT_MODES.TRIM) || isEditMode(EDIT_MODES.RIPPLE) || isEditMode(EDIT_MODES.ROLL)) && (isSelected || isDragging) const handleMouseDown = useCallback( (edge: "start" | "end", e: React.MouseEvent) => { if (disabled) return e.preventDefault() e.stopPropagation() setIsDragging(true) setDragEdge(edge) setDragStartX(e.clientX) onTrimStart(edge, e.clientX) }, [disabled, onTrimStart], ) useEffect(() => { if (!isDragging) return const handleMouseMove = (e: MouseEvent) => { const deltaX = e.clientX - dragStartX onTrimMove(deltaX) } const handleMouseUp = (e: MouseEvent) => { const committed = !e.shiftKey // Shift+release cancels the operation onTrimEnd(committed) setIsDragging(false) setDragEdge(null) } document.addEventListener("mousemove", handleMouseMove) document.addEventListener("mouseup", handleMouseUp) return () => { document.removeEventListener("mousemove", handleMouseMove) document.removeEventListener("mouseup", handleMouseUp) } }, [isDragging, dragStartX, onTrimMove, onTrimEnd]) if (!showHandles) return null return ( <> {/* Start handle */}
handleMouseDown("start", e)} data-trim-handle="start" data-testid="trim-handle-start" >
{/* End handle */}
handleMouseDown("end", e)} data-trim-handle="end" data-testid="trim-handle-end" >
{/* Visual feedback during trim */} {isDragging && (
)} ) } // Ripple handle variant with different styling interface RippleHandleProps extends ClipTrimHandlesProps { showRippleIndicator?: boolean } export function RippleHandles({ showRippleIndicator = true, ...props }: RippleHandleProps) { const { isEditMode } = useEditModeContext() if (!isEditMode(EDIT_MODES.RIPPLE)) { return } return (
{/* Ripple indicator arrow */} {showRippleIndicator && props.isSelected && (
)}
) } // Roll edit handles - connects two adjacent clips interface RollHandlesProps { leftClipId: string rightClipId: string position: number // Position between clips in pixels onRollStart: (initialX: number) => void onRollMove: (deltaX: number) => void onRollEnd: (committed: boolean) => void isActive: boolean disabled?: boolean } export function RollHandles({ position, onRollStart, onRollMove, onRollEnd, isActive, disabled = false, }: RollHandlesProps) { const { isEditMode } = useEditModeContext() const [isDragging, setIsDragging] = useState(false) const [dragStartX, setDragStartX] = useState(0) const handleMouseDown = (e: React.MouseEvent) => { if (disabled) return e.preventDefault() e.stopPropagation() setIsDragging(true) setDragStartX(e.clientX) onRollStart(e.clientX) } useEffect(() => { if (!isDragging) return const handleMouseMove = (e: MouseEvent) => { const deltaX = e.clientX - dragStartX onRollMove(deltaX) } const handleMouseUp = (e: MouseEvent) => { const committed = !e.shiftKey onRollEnd(committed) setIsDragging(false) } document.addEventListener("mousemove", handleMouseMove) document.addEventListener("mouseup", handleMouseUp) return () => { document.removeEventListener("mousemove", handleMouseMove) document.removeEventListener("mouseup", handleMouseUp) } }, [isDragging, dragStartX, onRollMove, onRollEnd]) if (!isEditMode(EDIT_MODES.ROLL) || !isActive) return null return (
{/* Roll indicator */}
) }