refactor(web): extract document drag listeners

This commit is contained in:
2026-04-01 10:39:28 +02:00
parent 84c5760392
commit eda8722d83
5 changed files with 91 additions and 24 deletions
@@ -0,0 +1,41 @@
import { describe, expect, it, vi } from "vitest";
import { attachDocumentMouseDrag } from "./timelineDocumentDrag.js";
describe("timelineDocumentDrag", () => {
it("registers move and up listeners and removes both during cleanup", () => {
const addEventListener = vi.fn();
const removeEventListener = vi.fn();
const onMove = vi.fn();
const onUp = vi.fn();
const cleanup = attachDocumentMouseDrag({ addEventListener, removeEventListener }, onMove, onUp);
expect(addEventListener).toHaveBeenCalledTimes(2);
expect(addEventListener).toHaveBeenNthCalledWith(1, "mousemove", onMove);
expect(addEventListener).toHaveBeenNthCalledWith(2, "mouseup", onUp);
cleanup();
expect(removeEventListener).toHaveBeenCalledTimes(2);
expect(removeEventListener).toHaveBeenNthCalledWith(1, "mousemove", onMove);
expect(removeEventListener).toHaveBeenNthCalledWith(2, "mouseup", onUp);
});
it("keeps cleanup idempotent from the helper perspective", () => {
const removeEventListener = vi.fn();
const cleanup = attachDocumentMouseDrag(
{
addEventListener: vi.fn(),
removeEventListener,
},
vi.fn(),
vi.fn(),
);
cleanup();
cleanup();
expect(removeEventListener).toHaveBeenCalledTimes(4);
});
});
@@ -0,0 +1,19 @@
type MouseDragDocumentLike = {
addEventListener: (type: "mousemove" | "mouseup", listener: (event: MouseEvent) => void) => void;
removeEventListener: (type: "mousemove" | "mouseup", listener: (event: MouseEvent) => void) => void;
};
export function attachDocumentMouseDrag(
doc: MouseDragDocumentLike,
onMove: (event: MouseEvent) => void,
onUp: (event: MouseEvent) => void,
): () => void {
function cleanup() {
doc.removeEventListener("mousemove", onMove);
doc.removeEventListener("mouseup", onUp);
}
doc.addEventListener("mousemove", onMove);
doc.addEventListener("mouseup", onUp);
return cleanup;
}
+5 -24
View File
@@ -24,6 +24,7 @@ import {
updateAllocationMultiDrag,
} from "./timelineAllocationMultiDrag.js";
import { createAllocationDragState } from "./timelineAllocationDragState.js";
import { attachDocumentMouseDrag } from "./timelineDocumentDrag.js";
import { buildProjectShiftMutationInput, createProjectDragState } from "./timelineProjectDrag.js";
import {
createMultiSelectState,
@@ -559,12 +560,7 @@ export function useTimelineDrag({
ev.preventDefault();
}
document.addEventListener("mousemove", handleMove);
document.addEventListener("mouseup", handleUp);
projectDragCleanupRef.current = () => {
document.removeEventListener("mousemove", handleMove);
document.removeEventListener("mouseup", handleUp);
};
projectDragCleanupRef.current = attachDocumentMouseDrag(document, handleMove, handleUp);
},
[finalizeProjectDrag, setProjectPreviewTargets, updateProjectDragPosition],
);
@@ -672,12 +668,7 @@ export function useTimelineDrag({
}
}
document.addEventListener("mousemove", handleMultiMove);
document.addEventListener("mouseup", handleMultiUp);
multiSelectCleanupRef.current = () => {
document.removeEventListener("mousemove", handleMultiMove);
document.removeEventListener("mouseup", handleMultiUp);
};
multiSelectCleanupRef.current = attachDocumentMouseDrag(document, handleMultiMove, handleMultiUp);
return;
}
@@ -796,12 +787,7 @@ export function useTimelineDrag({
setAllocDragState(INITIAL_ALLOC_DRAG);
}
document.addEventListener("mousemove", handleMove);
document.addEventListener("mouseup", handleUp);
allocDragCleanupRef.current = () => {
document.removeEventListener("mousemove", handleMove);
document.removeEventListener("mouseup", handleUp);
};
allocDragCleanupRef.current = attachDocumentMouseDrag(document, handleMove, handleUp);
},
[
clearPendingOptimisticAllocation,
@@ -943,12 +929,7 @@ export function useTimelineDrag({
setMultiSelectState(finished);
}
document.addEventListener("mousemove", handleMove);
document.addEventListener("mouseup", handleUp);
multiSelectCleanupRef.current = () => {
document.removeEventListener("mousemove", handleMove);
document.removeEventListener("mouseup", handleUp);
};
multiSelectCleanupRef.current = attachDocumentMouseDrag(document, handleMove, handleUp);
}, []);
const clearMultiSelect = useCallback(() => {