/** * Pure math utilities for timeline drag operations. * Extracted from useTimelineDrag to make the conversion logic testable * and reusable across different drag modes. */ /** Convert a pixel delta to a number of whole days based on cell width. */ export function pixelsToDays(deltaX: number, cellWidth: number): number { if (!Number.isFinite(cellWidth) || cellWidth <= 0) { return 0; } return Math.round(deltaX / cellWidth); } /** * Shift a date by a given number of days, returning a new Date. * Does not mutate the input. */ export function shiftDate(date: Date, daysDelta: number): Date { const result = new Date(date); result.setDate(result.getDate() + daysDelta); return result; } /** * Compute new start/end dates for a drag operation. * Handles move, resize-start, and resize-end modes with clamping * to prevent start from crossing past end (or vice versa). */ export function computeDragDates( mode: "move" | "resize-start" | "resize-end", originalStart: Date, originalEnd: Date, daysDelta: number, ): { start: Date; end: Date } { const newStart = new Date(originalStart); const newEnd = new Date(originalEnd); if (mode === "move") { newStart.setDate(newStart.getDate() + daysDelta); newEnd.setDate(newEnd.getDate() + daysDelta); } else if (mode === "resize-start") { newStart.setDate(newStart.getDate() + daysDelta); // Clamp: allow same-day but prevent crossing if (newStart > newEnd) newStart.setTime(newEnd.getTime()); } else { // resize-end newEnd.setDate(newEnd.getDate() + daysDelta); // Clamp: allow same-day but prevent crossing if (newEnd < newStart) newEnd.setTime(newStart.getTime()); } return { start: newStart, end: newEnd }; }