@@ -235,8 +235,8 @@ function handleDateSelect(arg: { start: Date; end: Date }) {
235235 .utc ()
236236 .tz (getUserTimezone (), true );
237237 const endLocal = getDayJsInstance ()(arg .end .toISOString ()).utc ().tz (getUserTimezone (), true );
238- const snappedStart = snapToGrid (startLocal , snap );
239- let snappedEnd = snapToGrid (endLocal , snap );
238+ const snappedStart = snapStartToGrid (startLocal , snap );
239+ let snappedEnd = snapEndToGrid (endLocal , snap );
240240 if (! snappedEnd .isAfter (snappedStart )) {
241241 snappedEnd = snappedStart .add (snap , ' minute' );
242242 }
@@ -255,10 +255,17 @@ function handleEventClick(arg: EventClickArg) {
255255 showEditTimeEntryModal .value = true ;
256256}
257257
258- // Snap a dayjs time to the nearest snap interval boundary
259- function snapToGrid (time : Dayjs , snapMinutes : number ): Dayjs {
258+ // Snap a dayjs time down to the previous snap boundary (for start times)
259+ function snapStartToGrid (time : Dayjs , snapMinutes : number ): Dayjs {
260260 const minutes = time .hour () * 60 + time .minute ();
261- const snapped = Math .round (minutes / snapMinutes ) * snapMinutes ;
261+ const snapped = Math .floor (minutes / snapMinutes ) * snapMinutes ;
262+ return time .startOf (' day' ).add (snapped , ' minute' );
263+ }
264+
265+ // Snap a dayjs time up to the next snap boundary (for end times)
266+ function snapEndToGrid(time : Dayjs , snapMinutes : number ): Dayjs {
267+ const minutes = time .hour () * 60 + time .minute ();
268+ const snapped = Math .ceil (minutes / snapMinutes ) * snapMinutes ;
262269 return time .startOf (' day' ).add (snapped , ' minute' );
263270}
264271
@@ -291,7 +298,7 @@ async function handleEventDrop(arg: EventDropArg) {
291298 .utc ()
292299 .tz (getUserTimezone (), true )
293300 .second (0 );
294- const snappedStart = snapToGrid (startLocal , snap );
301+ const snappedStart = snapStartToGrid (startLocal , snap );
295302 const durationMs = getLocalizedDayJs (timeEntry .end ).diff (getLocalizedDayJs (timeEntry .start ));
296303 const snappedEnd = snappedStart .add (durationMs , ' millisecond' );
297304 // Set FC event to snapped position immediately to avoid flash
@@ -325,8 +332,8 @@ async function handleEventResize(arg: EventChangeArg) {
325332 const startChanged = ! newStartLocal .isSame (origStartLocal , ' minute' );
326333
327334 // Snap only the changed edge once, reuse for both setDates and API update
328- const snappedStart = startChanged ? snapToGrid (newStartLocal , snap ) : null ;
329- const snappedEnd = ! startChanged && ! ext .isRunning ? snapToGrid (newEndLocal , snap ) : null ;
335+ const snappedStart = startChanged ? snapStartToGrid (newStartLocal , snap ) : null ;
336+ const snappedEnd = ! startChanged && ! ext .isRunning ? snapEndToGrid (newEndLocal , snap ) : null ;
330337
331338 // Set FC event to snapped position immediately to avoid flash.
332339 // Use the original event date for the edge that wasn't resized.
@@ -747,6 +754,10 @@ onUnmounted(() => {
747754 border : 1px solid var (--primary );
748755}
749756
757+ .fullcalendar :deep(.fc-event-mirror ) {
758+ pointer-events : none ;
759+ }
760+
750761.fullcalendar :deep(.fc-scrollgrid ) {
751762 border : 1px solid var (--border );
752763 border-left : 1px solid transparent ;
0 commit comments