mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 21:01:08 +02:00
fix(calendar): prevent event resize jump on drag start
Add resizeOffsetMinutes to track the difference between the snapped click position and the actual event boundary. This prevents events from immediately jumping when starting to resize via the top or bottom handles. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
6bea47d4da
commit
cab1905a2c
3 changed files with 61 additions and 14 deletions
|
|
@ -140,6 +140,7 @@
|
|||
let resizeOriginalEnd = $state<Date | null>(null);
|
||||
let resizePreviewTop = $state(0);
|
||||
let resizePreviewHeight = $state(0);
|
||||
let resizeOffsetMinutes = $state(0);
|
||||
|
||||
// Track if we actually moved during drag/resize (to prevent click on simple mousedown/up)
|
||||
let hasMoved = $state(false);
|
||||
|
|
@ -276,10 +277,19 @@
|
|||
resizeOriginalEnd = end;
|
||||
|
||||
const startMinutes = start.getHours() * 60 + start.getMinutes();
|
||||
const endMinutes = end.getHours() * 60 + end.getMinutes();
|
||||
const duration = differenceInMinutes(end, start);
|
||||
resizePreviewTop = minutesToPercent(startMinutes);
|
||||
resizePreviewHeight = (duration / (totalVisibleHours * 60)) * 100;
|
||||
|
||||
// Calculate offset between snapped click position and actual event boundary
|
||||
const clickMinutes = getMinutesFromY(e.clientY);
|
||||
if (edge === 'top') {
|
||||
resizeOffsetMinutes = clickMinutes - startMinutes;
|
||||
} else {
|
||||
resizeOffsetMinutes = clickMinutes - endMinutes;
|
||||
}
|
||||
|
||||
document.addEventListener('pointermove', handleResizeMove);
|
||||
document.addEventListener('pointerup', handleResizeEnd);
|
||||
}
|
||||
|
|
@ -289,18 +299,19 @@
|
|||
|
||||
hasMoved = true;
|
||||
const mouseMinutes = getMinutesFromY(e.clientY);
|
||||
const snappedMinutes = snapToGrid(mouseMinutes);
|
||||
// Apply offset to prevent jumping when drag starts
|
||||
const adjustedMinutes = snapToGrid(mouseMinutes - resizeOffsetMinutes);
|
||||
|
||||
const origStartMinutes = resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes();
|
||||
const origEndMinutes = resizeOriginalEnd.getHours() * 60 + resizeOriginalEnd.getMinutes();
|
||||
|
||||
if (resizeEdge === 'top') {
|
||||
const newStartMinutes = Math.min(snappedMinutes, origEndMinutes - SNAP_MINUTES);
|
||||
const newStartMinutes = Math.min(adjustedMinutes, origEndMinutes - SNAP_MINUTES);
|
||||
const clampedStart = Math.max(firstVisibleHour * 60, newStartMinutes);
|
||||
resizePreviewTop = minutesToPercent(clampedStart);
|
||||
resizePreviewHeight = ((origEndMinutes - clampedStart) / (totalVisibleHours * 60)) * 100;
|
||||
} else {
|
||||
const newEndMinutes = Math.max(snappedMinutes, origStartMinutes + SNAP_MINUTES);
|
||||
const newEndMinutes = Math.max(adjustedMinutes, origStartMinutes + SNAP_MINUTES);
|
||||
const clampedEnd = Math.min(lastVisibleHour * 60, newEndMinutes);
|
||||
resizePreviewHeight = ((clampedEnd - origStartMinutes) / (totalVisibleHours * 60)) * 100;
|
||||
}
|
||||
|
|
@ -313,7 +324,8 @@
|
|||
}
|
||||
|
||||
const mouseMinutes = getMinutesFromY(e.clientY);
|
||||
const snappedMinutes = snapToGrid(mouseMinutes);
|
||||
// Apply offset to prevent jumping
|
||||
const adjustedMinutes = snapToGrid(mouseMinutes - resizeOffsetMinutes);
|
||||
|
||||
const origStartMinutes = resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes();
|
||||
const origEndMinutes = resizeOriginalEnd.getHours() * 60 + resizeOriginalEnd.getMinutes();
|
||||
|
|
@ -324,7 +336,7 @@
|
|||
if (resizeEdge === 'top') {
|
||||
const newStartMinutes = Math.max(
|
||||
firstVisibleHour * 60,
|
||||
Math.min(snappedMinutes, origEndMinutes - SNAP_MINUTES)
|
||||
Math.min(adjustedMinutes, origEndMinutes - SNAP_MINUTES)
|
||||
);
|
||||
newStart = setHours(new Date(viewStore.currentDate), Math.floor(newStartMinutes / 60));
|
||||
newStart = setMinutes(newStart, newStartMinutes % 60);
|
||||
|
|
@ -332,7 +344,7 @@
|
|||
} else {
|
||||
const newEndMinutes = Math.min(
|
||||
lastVisibleHour * 60,
|
||||
Math.max(snappedMinutes, origStartMinutes + SNAP_MINUTES)
|
||||
Math.max(adjustedMinutes, origStartMinutes + SNAP_MINUTES)
|
||||
);
|
||||
newEnd = setHours(new Date(viewStore.currentDate), Math.floor(newEndMinutes / 60));
|
||||
newEnd = setMinutes(newEnd, newEndMinutes % 60);
|
||||
|
|
@ -362,6 +374,7 @@
|
|||
resizeEvent = null;
|
||||
resizeOriginalStart = null;
|
||||
resizeOriginalEnd = null;
|
||||
resizeOffsetMinutes = 0;
|
||||
hasMoved = false;
|
||||
document.removeEventListener('pointermove', handleDragMove);
|
||||
document.removeEventListener('pointerup', handleDragEnd);
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@
|
|||
let resizeOriginalEnd = $state<Date | null>(null);
|
||||
let resizePreviewTop = $state(0);
|
||||
let resizePreviewHeight = $state(0);
|
||||
let resizeOffsetMinutes = $state(0);
|
||||
|
||||
// Track if we actually moved during drag/resize (to prevent click on simple mousedown/up)
|
||||
let hasMoved = $state(false);
|
||||
|
|
@ -431,10 +432,19 @@
|
|||
|
||||
// Set initial preview
|
||||
const startMinutes = start.getHours() * 60 + start.getMinutes();
|
||||
const endMinutes = end.getHours() * 60 + end.getMinutes();
|
||||
const duration = differenceInMinutes(end, start);
|
||||
resizePreviewTop = minutesToPercent(startMinutes);
|
||||
resizePreviewHeight = (duration / (totalVisibleHours * 60)) * 100;
|
||||
|
||||
// Calculate offset between snapped click position and actual event boundary
|
||||
const clickMinutes = getMinutesFromY(e.clientY);
|
||||
if (edge === 'top') {
|
||||
resizeOffsetMinutes = clickMinutes - startMinutes;
|
||||
} else {
|
||||
resizeOffsetMinutes = clickMinutes - endMinutes;
|
||||
}
|
||||
|
||||
document.addEventListener('pointermove', handleResizeMove);
|
||||
document.addEventListener('pointerup', handleResizeEnd);
|
||||
}
|
||||
|
|
@ -444,6 +454,8 @@
|
|||
|
||||
hasMoved = true;
|
||||
const currentMinutes = getMinutesFromY(e.clientY);
|
||||
// Apply offset to prevent jumping when drag starts
|
||||
const adjustedMinutes = currentMinutes - resizeOffsetMinutes;
|
||||
const originalStartMinutes =
|
||||
resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes();
|
||||
const originalEndMinutes = resizeOriginalEnd.getHours() * 60 + resizeOriginalEnd.getMinutes();
|
||||
|
|
@ -452,7 +464,7 @@
|
|||
// Resize from bottom - change end time
|
||||
const newEndMinutes = Math.max(
|
||||
originalStartMinutes + 15,
|
||||
Math.min(lastVisibleHour * 60, currentMinutes)
|
||||
Math.min(lastVisibleHour * 60, adjustedMinutes)
|
||||
);
|
||||
const newDuration = newEndMinutes - originalStartMinutes;
|
||||
resizePreviewHeight = (newDuration / (totalVisibleHours * 60)) * 100;
|
||||
|
|
@ -460,7 +472,7 @@
|
|||
// Resize from top - change start time
|
||||
const newStartMinutes = Math.max(
|
||||
firstVisibleHour * 60,
|
||||
Math.min(originalEndMinutes - 15, currentMinutes)
|
||||
Math.min(originalEndMinutes - 15, adjustedMinutes)
|
||||
);
|
||||
const newDuration = originalEndMinutes - newStartMinutes;
|
||||
resizePreviewTop = minutesToPercent(newStartMinutes);
|
||||
|
|
@ -477,11 +489,14 @@
|
|||
resizeEvent = null;
|
||||
resizeOriginalStart = null;
|
||||
resizeOriginalEnd = null;
|
||||
resizeOffsetMinutes = 0;
|
||||
hasMoved = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const currentMinutes = getMinutesFromY(e.clientY);
|
||||
// Apply offset to prevent jumping
|
||||
const adjustedMinutes = currentMinutes - resizeOffsetMinutes;
|
||||
const originalStartMinutes =
|
||||
resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes();
|
||||
const originalEndMinutes = resizeOriginalEnd.getHours() * 60 + resizeOriginalEnd.getMinutes();
|
||||
|
|
@ -492,7 +507,7 @@
|
|||
if (resizeEdge === 'bottom') {
|
||||
const newEndMinutes = Math.max(
|
||||
originalStartMinutes + 15,
|
||||
Math.min(lastVisibleHour * 60, currentMinutes)
|
||||
Math.min(lastVisibleHour * 60, adjustedMinutes)
|
||||
);
|
||||
const newHours = Math.floor(newEndMinutes / 60);
|
||||
const newMins = newEndMinutes % 60;
|
||||
|
|
@ -501,7 +516,7 @@
|
|||
} else {
|
||||
const newStartMinutes = Math.max(
|
||||
firstVisibleHour * 60,
|
||||
Math.min(originalEndMinutes - 15, currentMinutes)
|
||||
Math.min(originalEndMinutes - 15, adjustedMinutes)
|
||||
);
|
||||
const newHours = Math.floor(newStartMinutes / 60);
|
||||
const newMins = newStartMinutes % 60;
|
||||
|
|
@ -527,6 +542,7 @@
|
|||
resizeEvent = null;
|
||||
resizeOriginalStart = null;
|
||||
resizeOriginalEnd = null;
|
||||
resizeOffsetMinutes = 0;
|
||||
hasMoved = false;
|
||||
}
|
||||
|
||||
|
|
@ -789,6 +805,7 @@
|
|||
resizeEvent = null;
|
||||
resizeOriginalStart = null;
|
||||
resizeOriginalEnd = null;
|
||||
resizeOffsetMinutes = 0;
|
||||
isTaskDragging = false;
|
||||
draggedTask = null;
|
||||
taskDragTargetDay = null;
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@
|
|||
let resizeOriginalEnd = $state<Date | null>(null);
|
||||
let resizePreviewTop = $state(0);
|
||||
let resizePreviewHeight = $state(0);
|
||||
let resizeOffsetMinutes = $state(0);
|
||||
|
||||
// Track if we actually moved during drag/resize (to prevent click on simple mousedown/up)
|
||||
let hasMoved = $state(false);
|
||||
|
|
@ -438,10 +439,19 @@
|
|||
|
||||
// Set initial preview
|
||||
const startMinutes = start.getHours() * 60 + start.getMinutes();
|
||||
const endMinutes = end.getHours() * 60 + end.getMinutes();
|
||||
const duration = differenceInMinutes(end, start);
|
||||
resizePreviewTop = minutesToPercent(startMinutes);
|
||||
resizePreviewHeight = (duration / (totalVisibleHours * 60)) * 100;
|
||||
|
||||
// Calculate offset between snapped click position and actual event boundary
|
||||
const clickMinutes = getMinutesFromY(e.clientY);
|
||||
if (edge === 'top') {
|
||||
resizeOffsetMinutes = clickMinutes - startMinutes;
|
||||
} else {
|
||||
resizeOffsetMinutes = clickMinutes - endMinutes;
|
||||
}
|
||||
|
||||
document.addEventListener('pointermove', handleResizeMove);
|
||||
document.addEventListener('pointerup', handleResizeEnd);
|
||||
}
|
||||
|
|
@ -451,6 +461,8 @@
|
|||
|
||||
hasMoved = true;
|
||||
const currentMinutes = getMinutesFromY(e.clientY);
|
||||
// Apply offset to prevent jumping when drag starts
|
||||
const adjustedMinutes = currentMinutes - resizeOffsetMinutes;
|
||||
const originalStartMinutes =
|
||||
resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes();
|
||||
const originalEndMinutes = resizeOriginalEnd.getHours() * 60 + resizeOriginalEnd.getMinutes();
|
||||
|
|
@ -459,7 +471,7 @@
|
|||
// Resize from bottom - change end time
|
||||
const newEndMinutes = Math.max(
|
||||
originalStartMinutes + 15,
|
||||
Math.min(lastVisibleHour * 60, currentMinutes)
|
||||
Math.min(lastVisibleHour * 60, adjustedMinutes)
|
||||
);
|
||||
const newDuration = newEndMinutes - originalStartMinutes;
|
||||
resizePreviewHeight = (newDuration / (totalVisibleHours * 60)) * 100;
|
||||
|
|
@ -467,7 +479,7 @@
|
|||
// Resize from top - change start time
|
||||
const newStartMinutes = Math.max(
|
||||
firstVisibleHour * 60,
|
||||
Math.min(originalEndMinutes - 15, currentMinutes)
|
||||
Math.min(originalEndMinutes - 15, adjustedMinutes)
|
||||
);
|
||||
const newDuration = originalEndMinutes - newStartMinutes;
|
||||
resizePreviewTop = minutesToPercent(newStartMinutes);
|
||||
|
|
@ -484,11 +496,14 @@
|
|||
resizeEvent = null;
|
||||
resizeOriginalStart = null;
|
||||
resizeOriginalEnd = null;
|
||||
resizeOffsetMinutes = 0;
|
||||
hasMoved = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const currentMinutes = getMinutesFromY(e.clientY);
|
||||
// Apply offset to prevent jumping
|
||||
const adjustedMinutes = currentMinutes - resizeOffsetMinutes;
|
||||
const originalStartMinutes =
|
||||
resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes();
|
||||
const originalEndMinutes = resizeOriginalEnd.getHours() * 60 + resizeOriginalEnd.getMinutes();
|
||||
|
|
@ -499,7 +514,7 @@
|
|||
if (resizeEdge === 'bottom') {
|
||||
const newEndMinutes = Math.max(
|
||||
originalStartMinutes + 15,
|
||||
Math.min(lastVisibleHour * 60, currentMinutes)
|
||||
Math.min(lastVisibleHour * 60, adjustedMinutes)
|
||||
);
|
||||
const newHours = Math.floor(newEndMinutes / 60);
|
||||
const newMins = newEndMinutes % 60;
|
||||
|
|
@ -508,7 +523,7 @@
|
|||
} else {
|
||||
const newStartMinutes = Math.max(
|
||||
firstVisibleHour * 60,
|
||||
Math.min(originalEndMinutes - 15, currentMinutes)
|
||||
Math.min(originalEndMinutes - 15, adjustedMinutes)
|
||||
);
|
||||
const newHours = Math.floor(newStartMinutes / 60);
|
||||
const newMins = newStartMinutes % 60;
|
||||
|
|
@ -534,6 +549,7 @@
|
|||
resizeEvent = null;
|
||||
resizeOriginalStart = null;
|
||||
resizeOriginalEnd = null;
|
||||
resizeOffsetMinutes = 0;
|
||||
hasMoved = false;
|
||||
}
|
||||
|
||||
|
|
@ -812,6 +828,7 @@
|
|||
resizeEvent = null;
|
||||
resizeOriginalStart = null;
|
||||
resizeOriginalEnd = null;
|
||||
resizeOffsetMinutes = 0;
|
||||
hasMoved = false;
|
||||
}
|
||||
// Cancel task drag/resize
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue