refactor(web): extract project drag session
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { beginProjectDragSession } from "./timelineProjectDragSession.js";
|
||||
|
||||
describe("timelineProjectDragSession", () => {
|
||||
it("starts the session, forwards movement, and finalizes on mouseup", () => {
|
||||
const previousCleanup = vi.fn();
|
||||
const setState = vi.fn();
|
||||
const updatePosition = vi.fn();
|
||||
const finalize = vi.fn();
|
||||
const state = { projectId: "project-1", isDragging: true };
|
||||
const cleanupRef = { current: previousCleanup as (() => void) | null };
|
||||
const stateRef = { current: { projectId: null, isDragging: false } };
|
||||
const handlers: {
|
||||
move?: (event: MouseEvent) => void;
|
||||
up?: (event: MouseEvent) => void;
|
||||
} = {};
|
||||
|
||||
const attachDrag = vi.fn((_: Document, onMove: (event: MouseEvent) => void, onUp: (event: MouseEvent) => void) => {
|
||||
handlers.move = onMove;
|
||||
handlers.up = onUp;
|
||||
return vi.fn();
|
||||
});
|
||||
|
||||
beginProjectDragSession({
|
||||
state,
|
||||
cleanupRef,
|
||||
stateRef,
|
||||
setState,
|
||||
documentTarget: {} as Document,
|
||||
attachDrag,
|
||||
updatePosition,
|
||||
finalize,
|
||||
});
|
||||
|
||||
expect(previousCleanup).toHaveBeenCalledOnce();
|
||||
expect(stateRef.current).toBe(state);
|
||||
expect(setState).toHaveBeenCalledWith(state);
|
||||
|
||||
handlers.move?.({ clientX: 42 } as MouseEvent);
|
||||
expect(updatePosition).toHaveBeenCalledWith(42);
|
||||
|
||||
const preventDefault = vi.fn();
|
||||
const attachedCleanup = cleanupRef.current;
|
||||
handlers.up?.({ clientX: 54, preventDefault } as unknown as MouseEvent);
|
||||
|
||||
expect(attachedCleanup).toHaveBeenCalledOnce();
|
||||
expect(cleanupRef.current).toBeNull();
|
||||
expect(finalize).toHaveBeenCalledWith(54);
|
||||
expect(preventDefault).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("works when no prior cleanup is registered", () => {
|
||||
const setState = vi.fn();
|
||||
const updatePosition = vi.fn();
|
||||
const finalize = vi.fn().mockResolvedValue(undefined);
|
||||
const cleanupRef = { current: null as (() => void) | null };
|
||||
const stateRef = { current: { projectId: null, isDragging: false } };
|
||||
let upHandler: ((event: MouseEvent) => void) | undefined;
|
||||
|
||||
beginProjectDragSession({
|
||||
state: { projectId: "project-2", isDragging: true },
|
||||
cleanupRef,
|
||||
stateRef,
|
||||
setState,
|
||||
documentTarget: {} as Document,
|
||||
attachDrag: (_documentTarget, _onMove, onUp) => {
|
||||
upHandler = onUp;
|
||||
return vi.fn();
|
||||
},
|
||||
updatePosition,
|
||||
finalize,
|
||||
});
|
||||
|
||||
upHandler?.({ clientX: 12, preventDefault() {} } as MouseEvent);
|
||||
|
||||
expect(updatePosition).not.toHaveBeenCalled();
|
||||
expect(finalize).toHaveBeenCalledWith(12);
|
||||
expect(cleanupRef.current).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
type MutableCurrent<T> = {
|
||||
current: T;
|
||||
};
|
||||
|
||||
type AttachDocumentMouseDrag = (
|
||||
documentTarget: Document,
|
||||
onMove: (event: MouseEvent) => void,
|
||||
onUp: (event: MouseEvent) => void,
|
||||
) => () => void;
|
||||
|
||||
type BeginProjectDragSessionParams<TState> = {
|
||||
state: TState;
|
||||
cleanupRef: MutableCurrent<(() => void) | null>;
|
||||
stateRef: MutableCurrent<TState>;
|
||||
setState: (state: TState) => void;
|
||||
documentTarget: Document;
|
||||
attachDrag: AttachDocumentMouseDrag;
|
||||
updatePosition: (clientX: number) => void;
|
||||
finalize: (clientX: number) => Promise<void> | void;
|
||||
};
|
||||
|
||||
export function beginProjectDragSession<TState>({
|
||||
state,
|
||||
cleanupRef,
|
||||
stateRef,
|
||||
setState,
|
||||
documentTarget,
|
||||
attachDrag,
|
||||
updatePosition,
|
||||
finalize,
|
||||
}: BeginProjectDragSessionParams<TState>) {
|
||||
stateRef.current = state;
|
||||
setState(state);
|
||||
cleanupRef.current?.();
|
||||
|
||||
function handleMove(event: MouseEvent) {
|
||||
updatePosition(event.clientX);
|
||||
}
|
||||
|
||||
function handleUp(event: MouseEvent) {
|
||||
cleanupRef.current?.();
|
||||
cleanupRef.current = null;
|
||||
void finalize(event.clientX);
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
cleanupRef.current = attachDrag(documentTarget, handleMove, handleUp);
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import { createAllocationDragState } from "./timelineAllocationDragState.js";
|
||||
import { cleanupTimelineDragState } from "./timelineDragCleanup.js";
|
||||
import { attachDocumentMouseDrag } from "./timelineDocumentDrag.js";
|
||||
import { buildProjectShiftMutationInput, createProjectDragState } from "./timelineProjectDrag.js";
|
||||
import { beginProjectDragSession } from "./timelineProjectDragSession.js";
|
||||
import {
|
||||
completeMultiSelectDraft,
|
||||
createMultiSelectState,
|
||||
@@ -550,24 +551,19 @@ export function useTimelineDrag({
|
||||
endDate: opts.endDate,
|
||||
startMouseX: e.clientX,
|
||||
});
|
||||
dragStateRef.current = state;
|
||||
setDragState(state);
|
||||
|
||||
setProjectPreviewTargets(opts.projectId, e.currentTarget);
|
||||
projectDragCleanupRef.current?.();
|
||||
|
||||
function handleMove(ev: MouseEvent) {
|
||||
updateProjectDragPosition(ev.clientX);
|
||||
}
|
||||
|
||||
function handleUp(ev: MouseEvent) {
|
||||
projectDragCleanupRef.current?.();
|
||||
projectDragCleanupRef.current = null;
|
||||
void finalizeProjectDrag(ev.clientX);
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
projectDragCleanupRef.current = attachDocumentMouseDrag(document, handleMove, handleUp);
|
||||
beginProjectDragSession({
|
||||
state,
|
||||
cleanupRef: projectDragCleanupRef,
|
||||
stateRef: dragStateRef,
|
||||
setState: setDragState,
|
||||
documentTarget: document,
|
||||
attachDrag: attachDocumentMouseDrag,
|
||||
updatePosition: updateProjectDragPosition,
|
||||
finalize: (clientX) => {
|
||||
void finalizeProjectDrag(clientX);
|
||||
},
|
||||
});
|
||||
},
|
||||
[finalizeProjectDrag, setProjectPreviewTargets, updateProjectDragPosition],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user