fix(timeline): cancel stranded drag interactions

This commit is contained in:
2026-04-01 14:57:56 +02:00
parent a71bbeb640
commit d4652b7a42
5 changed files with 240 additions and 3 deletions
+54 -2
View File
@@ -20,7 +20,7 @@ import { beginAllocationMultiDragSession } from "./timelineAllocationMultiDragSe
import { createAllocationDragState } from "./timelineAllocationDragState.js";
import { beginAllocationDragSession } from "./timelineAllocationDragSession.js";
import { finalizeAllocationReleaseEffects } from "./timelineAllocationReleaseEffects.js";
import { cleanupTimelineDragState } from "./timelineDragCleanup.js";
import { cancelTransientMultiSelectState, cleanupTimelineDragState } from "./timelineDragCleanup.js";
import { resolveAllocationDragPosition, resolveProjectDragPosition } from "./timelineDragPosition.js";
import { attachDocumentMouseDrag } from "./timelineDocumentDrag.js";
import { finalizeProjectDrag } from "./timelineProjectDragFinalize.js";
@@ -350,6 +350,43 @@ export function useTimelineDrag({
setDragState(INITIAL_DRAG_STATE);
}, []);
const cancelActiveInteractions = useCallback(() => {
const hasActiveInteraction =
dragStateRef.current.isDragging ||
allocDragRef.current.isActive ||
rangeStateRef.current.isSelecting ||
multiSelectRef.current.isSelecting ||
multiSelectRef.current.isMultiDragging;
if (!hasActiveInteraction) return;
const nextMultiSelectState = cancelTransientMultiSelectState(
multiSelectRef.current,
INITIAL_MULTI_SELECT,
);
cleanupTimelineDragState({
projectDragCleanupRef,
allocDragCleanupRef,
multiSelectCleanupRef,
projectPreviewRef,
allocPreviewRef,
dragStateRef,
allocDragRef,
rangeStateRef,
multiSelectRef,
initialDragState: INITIAL_DRAG_STATE,
initialAllocDragState: INITIAL_ALLOC_DRAG,
initialRangeState: INITIAL_RANGE_STATE,
initialMultiSelectState: nextMultiSelectState,
clearPreview: clearLivePreview,
});
setDragState(INITIAL_DRAG_STATE);
setAllocDragState(INITIAL_ALLOC_DRAG);
setRangeState(INITIAL_RANGE_STATE);
setMultiSelectState(nextMultiSelectState);
}, []);
// Project-shift preview
const { data: previewData, isFetching: isPreviewLoading } = trpc.timeline.previewShift.useQuery(
{
@@ -831,7 +868,22 @@ export function useTimelineDrag({
);
useEffect(() => {
function handleWindowBlur() {
cancelActiveInteractions();
}
function handleVisibilityChange() {
if (document.visibilityState === "hidden") {
cancelActiveInteractions();
}
}
window.addEventListener("blur", handleWindowBlur);
document.addEventListener("visibilitychange", handleVisibilityChange);
return () => {
window.removeEventListener("blur", handleWindowBlur);
document.removeEventListener("visibilitychange", handleVisibilityChange);
cleanupTimelineDragState({
projectDragCleanupRef,
allocDragCleanupRef,
@@ -849,7 +901,7 @@ export function useTimelineDrag({
clearPreview: clearLivePreview,
});
};
}, []);
}, [cancelActiveInteractions]);
// ── Derived ─────────────────────────────────────────────────────────────────