refactor(web): extract timeline drag cleanup

This commit is contained in:
2026-04-01 11:12:20 +02:00
parent 922394c56a
commit b14be80e32
5 changed files with 199 additions and 14 deletions
@@ -0,0 +1,85 @@
import { describe, expect, it, vi } from "vitest";
import { cleanupTimelineDragState } from "./timelineDragCleanup.js";
describe("timelineDragCleanup", () => {
it("runs registered cleanup callbacks, clears previews, and resets refs", () => {
const projectCleanup = vi.fn();
const allocCleanup = vi.fn();
const multiCleanup = vi.fn();
const projectPreview = { id: "project-preview" };
const allocPreview = { id: "alloc-preview" };
const clearPreview = vi.fn();
const projectDragCleanupRef = { current: projectCleanup };
const allocDragCleanupRef = { current: allocCleanup };
const multiSelectCleanupRef = { current: multiCleanup };
const projectPreviewRef = { current: projectPreview };
const allocPreviewRef = { current: allocPreview };
const dragStateRef = { current: { drag: true } };
const allocDragRef = { current: { alloc: true } };
const rangeStateRef = { current: { range: true } };
const multiSelectRef = { current: { multi: true } };
const initialDragState = { drag: false };
const initialAllocDragState = { alloc: false };
const initialRangeState = { range: false };
const initialMultiSelectState = { multi: false };
cleanupTimelineDragState({
projectDragCleanupRef,
allocDragCleanupRef,
multiSelectCleanupRef,
projectPreviewRef,
allocPreviewRef,
dragStateRef,
allocDragRef,
rangeStateRef,
multiSelectRef,
initialDragState,
initialAllocDragState,
initialRangeState,
initialMultiSelectState,
clearPreview,
});
expect(projectCleanup).toHaveBeenCalledOnce();
expect(allocCleanup).toHaveBeenCalledOnce();
expect(multiCleanup).toHaveBeenCalledOnce();
expect(clearPreview).toHaveBeenNthCalledWith(1, projectPreview);
expect(clearPreview).toHaveBeenNthCalledWith(2, allocPreview);
expect(projectDragCleanupRef.current).toBeNull();
expect(allocDragCleanupRef.current).toBeNull();
expect(multiSelectCleanupRef.current).toBeNull();
expect(projectPreviewRef.current).toBeNull();
expect(allocPreviewRef.current).toBeNull();
expect(dragStateRef.current).toBe(initialDragState);
expect(allocDragRef.current).toBe(initialAllocDragState);
expect(rangeStateRef.current).toBe(initialRangeState);
expect(multiSelectRef.current).toBe(initialMultiSelectState);
});
it("tolerates missing cleanups and preview sessions", () => {
const clearPreview = vi.fn();
cleanupTimelineDragState({
projectDragCleanupRef: { current: null },
allocDragCleanupRef: { current: null },
multiSelectCleanupRef: { current: null },
projectPreviewRef: { current: null },
allocPreviewRef: { current: null },
dragStateRef: { current: { drag: true } },
allocDragRef: { current: { alloc: true } },
rangeStateRef: { current: { range: true } },
multiSelectRef: { current: { multi: true } },
initialDragState: { drag: false },
initialAllocDragState: { alloc: false },
initialRangeState: { range: false },
initialMultiSelectState: { multi: false },
clearPreview,
});
expect(clearPreview).toHaveBeenCalledTimes(2);
expect(clearPreview).toHaveBeenNthCalledWith(1, null);
expect(clearPreview).toHaveBeenNthCalledWith(2, null);
});
});
+74
View File
@@ -0,0 +1,74 @@
type MutableCurrent<T> = {
current: T;
};
type TimelineDragCleanupParams<
DragState,
AllocDragState,
RangeState,
MultiSelectState,
PreviewSession,
> = {
projectDragCleanupRef: MutableCurrent<(() => void) | null>;
allocDragCleanupRef: MutableCurrent<(() => void) | null>;
multiSelectCleanupRef: MutableCurrent<(() => void) | null>;
projectPreviewRef: MutableCurrent<PreviewSession | null>;
allocPreviewRef: MutableCurrent<PreviewSession | null>;
dragStateRef: MutableCurrent<DragState>;
allocDragRef: MutableCurrent<AllocDragState>;
rangeStateRef: MutableCurrent<RangeState>;
multiSelectRef: MutableCurrent<MultiSelectState>;
initialDragState: DragState;
initialAllocDragState: AllocDragState;
initialRangeState: RangeState;
initialMultiSelectState: MultiSelectState;
clearPreview: (session: PreviewSession | null) => void;
};
export function cleanupTimelineDragState<
DragState,
AllocDragState,
RangeState,
MultiSelectState,
PreviewSession,
>({
projectDragCleanupRef,
allocDragCleanupRef,
multiSelectCleanupRef,
projectPreviewRef,
allocPreviewRef,
dragStateRef,
allocDragRef,
rangeStateRef,
multiSelectRef,
initialDragState,
initialAllocDragState,
initialRangeState,
initialMultiSelectState,
clearPreview,
}: TimelineDragCleanupParams<
DragState,
AllocDragState,
RangeState,
MultiSelectState,
PreviewSession
>) {
projectDragCleanupRef.current?.();
allocDragCleanupRef.current?.();
multiSelectCleanupRef.current?.();
projectDragCleanupRef.current = null;
allocDragCleanupRef.current = null;
multiSelectCleanupRef.current = null;
clearPreview(projectPreviewRef.current);
clearPreview(allocPreviewRef.current);
projectPreviewRef.current = null;
allocPreviewRef.current = null;
dragStateRef.current = initialDragState;
allocDragRef.current = initialAllocDragState;
rangeStateRef.current = initialRangeState;
multiSelectRef.current = initialMultiSelectState;
}
+17 -14
View File
@@ -20,6 +20,7 @@ import {
} from "./timelineAllocationMultiDrag.js";
import { resolveAllocationRelease } from "./timelineAllocationRelease.js";
import { createAllocationDragState } from "./timelineAllocationDragState.js";
import { cleanupTimelineDragState } from "./timelineDragCleanup.js";
import { attachDocumentMouseDrag } from "./timelineDocumentDrag.js";
import { buildProjectShiftMutationInput, createProjectDragState } from "./timelineProjectDrag.js";
import {
@@ -1005,20 +1006,22 @@ export function useTimelineDrag({
useEffect(() => {
return () => {
projectDragCleanupRef.current?.();
allocDragCleanupRef.current?.();
multiSelectCleanupRef.current?.();
projectDragCleanupRef.current = null;
allocDragCleanupRef.current = null;
multiSelectCleanupRef.current = null;
clearLivePreview(projectPreviewRef.current);
clearLivePreview(allocPreviewRef.current);
projectPreviewRef.current = null;
allocPreviewRef.current = null;
dragStateRef.current = INITIAL_DRAG_STATE;
allocDragRef.current = INITIAL_ALLOC_DRAG;
rangeStateRef.current = INITIAL_RANGE_STATE;
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: INITIAL_MULTI_SELECT,
clearPreview: clearLivePreview,
});
};
}, []);