fix: ensure dragging/resizing an element on the timeline doesn't deselect it

This commit is contained in:
Maze Winther 2025-07-19 16:19:06 +02:00
parent 0d95f032cc
commit 42ae8a7530
3 changed files with 69 additions and 34 deletions

View File

@ -78,7 +78,6 @@ export function Timeline() {
snappingEnabled,
toggleSnapping,
dragState,
justFinishedDragging,
} = useTimelineStore();
const { mediaItems, addMediaItem } = useMediaStore();
const { activeProject } = useProjectStore();
@ -92,6 +91,14 @@ export function Timeline() {
const rulerRef = useRef<HTMLDivElement>(null);
const [isInTimeline, setIsInTimeline] = useState(false);
// Track mouse down/up for distinguishing clicks from drag/resize ends
const mouseTrackingRef = useRef({
isMouseDown: false,
downX: 0,
downY: 0,
downTime: 0,
});
// Timeline zoom functionality
const { zoomLevel, setZoomLevel, handleWheel } = useTimelineZoom({
containerRef: timelineRef,
@ -158,16 +165,69 @@ export function Timeline() {
setCurrentSnapPoint(snapPoint);
}, []);
// Track mouse down to distinguish real clicks from drag/resize ends
const handleTimelineMouseDown = useCallback((e: React.MouseEvent) => {
// Only track mouse down on timeline background areas (not elements)
const target = e.target as HTMLElement;
const isTimelineBackground =
!target.closest(".timeline-element") &&
!playheadRef.current?.contains(target) &&
!target.closest("[data-track-labels]");
if (isTimelineBackground) {
mouseTrackingRef.current = {
isMouseDown: true,
downX: e.clientX,
downY: e.clientY,
downTime: e.timeStamp,
};
}
}, []);
// Timeline content click to seek handler
const handleTimelineContentClick = useCallback(
(e: React.MouseEvent) => {
// Don't seek if this was a selection box operation
if (isSelecting || justFinishedSelecting) {
const { isMouseDown, downX, downY, downTime } = mouseTrackingRef.current;
// Reset mouse tracking
mouseTrackingRef.current = {
isMouseDown: false,
downX: 0,
downY: 0,
downTime: 0,
};
// Only process as click if we tracked a mouse down on timeline background
if (!isMouseDown) {
console.log(
JSON.stringify({
ignoredClickWithoutMouseDown: true,
timeStamp: e.timeStamp,
})
);
return;
}
// Don't seek if we just finished dragging an element
if (justFinishedDragging) {
// Check if mouse moved significantly (indicates drag, not click)
const deltaX = Math.abs(e.clientX - downX);
const deltaY = Math.abs(e.clientY - downY);
const deltaTime = e.timeStamp - downTime;
if (deltaX > 5 || deltaY > 5 || deltaTime > 500) {
console.log(
JSON.stringify({
ignoredDragNotClick: true,
deltaX,
deltaY,
deltaTime,
timeStamp: e.timeStamp,
})
);
return;
}
// Don't seek if this was a selection box operation
if (isSelecting || justFinishedSelecting) {
return;
}
@ -187,19 +247,6 @@ export function Timeline() {
return;
}
// MAIN FIX: Only seek on direct clicks to timeline background areas
// Check if the click is on the actual timeline background, not on any interactive elements
const target = e.target as HTMLElement;
const isTimelineBackground =
target.classList.contains("track-elements-container") ||
target.closest("[data-ruler-area]") ||
target.classList.contains("timeline-track-background");
if (!isTimelineBackground) {
clearSelectedElements();
return;
}
// Clear selected elements when clicking empty timeline area
console.log(JSON.stringify({ clearingSelectedElements: true }));
clearSelectedElements();
@ -256,7 +303,6 @@ export function Timeline() {
clearSelectedElements,
isSelecting,
justFinishedSelecting,
justFinishedDragging,
]
);
@ -931,7 +977,10 @@ export function Timeline() {
<div
className="flex-1 relative overflow-hidden"
onWheel={handleWheel}
onMouseDown={handleSelectionMouseDown}
onMouseDown={(e) => {
handleTimelineMouseDown(e);
handleSelectionMouseDown(e);
}}
onClick={handleTimelineContentClick}
ref={tracksContainerRef}
>

View File

@ -186,12 +186,6 @@ export function useTimelineElementResize({
};
const handleResizeEnd = () => {
console.log(
JSON.stringify({
resizeEnd: true,
timeStamp: Date.now(),
})
);
setResizing(null);
};

View File

@ -74,7 +74,6 @@ interface TimelineStore {
clickOffsetTime: number;
currentTime: number;
};
justFinishedDragging: boolean;
setDragState: (dragState: Partial<TimelineStore["dragState"]>) => void;
startDrag: (
elementId: string,
@ -231,8 +230,6 @@ export const useTimelineStore = create<TimelineStore>((set, get) => {
// Snapping settings defaults
snappingEnabled: true,
justFinishedDragging: false,
getSortedTracks: () => {
const { _tracks } = get();
const tracksWithMain = ensureMainTrack(_tracks);
@ -941,12 +938,7 @@ export const useTimelineStore = create<TimelineStore>((set, get) => {
clickOffsetTime: 0,
currentTime: 0,
},
justFinishedDragging: true,
});
setTimeout(() => {
set({ justFinishedDragging: false });
}, 50);
},
// Persistence methods