Merge public ihasmail: drag calendar events to another day in the week grid (#400)
This commit is contained in:
@@ -10,6 +10,8 @@ import {
|
||||
snap,
|
||||
movePatch,
|
||||
moveByDaysPatch,
|
||||
moveAcrossPatch,
|
||||
columnsMoved,
|
||||
dayDelta,
|
||||
resizePatch,
|
||||
SNAP_MINUTES,
|
||||
@@ -180,10 +182,22 @@ describe("the patch a drag sends, computed in the event's own frame", () => {
|
||||
expect(resizePatch(3600, -600)).toEqual({ duration: "PT15M" });
|
||||
});
|
||||
|
||||
it("moves by days and minutes together, as a week-grid drag does", () => {
|
||||
expect(moveAcrossPatch("2026-09-04T14:00:00", 2, 90)).toEqual({ start: "2026-09-06T15:30:00" });
|
||||
expect(moveAcrossPatch("2026-09-04T14:00:00", -1, 0)).toEqual({ start: "2026-09-03T14:00:00" });
|
||||
expect(moveAcrossPatch("2026-09-04T14:00:00", 0, -30)).toEqual({ start: "2026-09-04T13:30:00" });
|
||||
});
|
||||
|
||||
it("adds the days as days, so a clock change does not move the hour", () => {
|
||||
// US clocks go back on 1 November 2026; 14:00 stays 14:00 across it.
|
||||
expect(moveAcrossPatch("2026-10-31T14:00:00", 2, 0)).toEqual({ start: "2026-11-02T14:00:00" });
|
||||
});
|
||||
|
||||
it("says nothing at all about a start it cannot read", () => {
|
||||
expect(movePatch("not a date", 30)).toEqual({});
|
||||
expect(moveByDaysPatch("", 3)).toEqual({});
|
||||
expect(moveByDaysPatch("2026-09-04T14:00:00", Number.NaN)).toEqual({});
|
||||
expect(moveAcrossPatch("not a date", 1, 30)).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -228,3 +242,23 @@ describe("pixelsToMinutes", () => {
|
||||
expect(SNAP_MINUTES).toBe(15);
|
||||
});
|
||||
});
|
||||
|
||||
describe("columnsMoved", () => {
|
||||
it("counts whole columns, to the nearest", () => {
|
||||
expect(columnsMoved(100, 100, 2, 7)).toBe(1);
|
||||
expect(columnsMoved(140, 100, 2, 7)).toBe(1);
|
||||
expect(columnsMoved(160, 100, 2, 7)).toBe(2);
|
||||
expect(columnsMoved(-40, 100, 2, 7)).toBe(0);
|
||||
expect(columnsMoved(-160, 100, 3, 7)).toBe(-2);
|
||||
});
|
||||
|
||||
it("stops at the edges of the week instead of wrapping", () => {
|
||||
expect(columnsMoved(-900, 100, 2, 7)).toBe(-2);
|
||||
expect(columnsMoved(900, 100, 2, 7)).toBe(4);
|
||||
});
|
||||
|
||||
it("never moves sideways in a one-day grid or before it is measured", () => {
|
||||
expect(columnsMoved(500, 100, 0, 1)).toBe(0);
|
||||
expect(columnsMoved(500, 0, 0, 7)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -142,6 +142,35 @@ export function moveByDaysPatch(storedStart: string, days: number): DragPatch {
|
||||
return { start: formatStored(moved) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Moved by whole days and by minutes at once -- the week grid, where a drag
|
||||
* goes sideways to another day and up or down to another hour in the same
|
||||
* gesture.
|
||||
*
|
||||
* The days go first and as days, for the reason moveByDaysPatch gives: a day
|
||||
* added to a wall clock keeps its time of day across a clock change, where
|
||||
* 1440 minutes would not.
|
||||
*/
|
||||
export function moveAcrossPatch(storedStart: string, days: number, deltaMinutes: number): DragPatch {
|
||||
const byDays = days ? moveByDaysPatch(storedStart, days).start : storedStart;
|
||||
if (!byDays) return {};
|
||||
return snap(deltaMinutes) ? movePatch(byDays, deltaMinutes) : { start: byDays };
|
||||
}
|
||||
|
||||
/**
|
||||
* How many columns sideways the pointer has gone, kept inside the grid.
|
||||
*
|
||||
* Counted from the column the drag began in, so an event that crosses
|
||||
* midnight moves by the same amount whichever of its two halves was picked
|
||||
* up. Past the first or last column it stops at the edge rather than
|
||||
* wrapping: the week on screen is the only week a drag can reach.
|
||||
*/
|
||||
export function columnsMoved(deltaPixels: number, columnWidth: number, fromIndex: number, columnCount: number): number {
|
||||
if (!columnWidth || columnCount < 2) return 0;
|
||||
const moved = Math.round(deltaPixels / columnWidth) || 0; // never -0
|
||||
return Math.max(-fromIndex, Math.min(columnCount - 1 - fromIndex, moved));
|
||||
}
|
||||
|
||||
/** Whole days between two local dates, ignoring the time of day on each. */
|
||||
export function dayDelta(from: Date, to: Date): number {
|
||||
const a = new Date(from.getFullYear(), from.getMonth(), from.getDate()).getTime();
|
||||
|
||||
Reference in New Issue
Block a user