Skip to main content
← Blog

Dragging Multi-Day Events in a Daily Timeline

VauDium ·

How we improved drag & drop for multi-day events that extend beyond the visible day in a daily timeline view. Clamping, resize handles, and visual feedback.

Dragging Multi-Day Events in a Daily Timeline

Fecit’s daily timeline view lays out a single day along the vertical axis. A 9 AM to 6 PM event shows up as a block spanning that range. Simple.

But what about an event that runs from March 29, 10 PM to March 31, 2 AM? On March 30’s timeline, that block fills the entire day, midnight to midnight. The real start and end are off-screen.

How do you make that draggable and resizable?

The Problem: Handles on Clamped Edges

Timeline blocks have resize handles at the top and bottom. Drag the top to change the start time, drag the bottom to change the end time. Drag the body to move the whole thing.

When a multi-day event gets clamped to the current day, things break down.

  • The top is clamped but the top handle is visible? The user will try to drag it. But the actual start is yesterday.
  • Both edges are clamped? The block fills the entire day. What should dragging it do?
  • Only one edge is clamped? If you drag-move the block, should only the unclamped edge move, or the whole event? Either way feels wrong.

The Solution: Behavior Based on Clamping State

DailyTimelineView computes each block with clampedTop and clampedBottom flags. These flags drive every decision.

1. Hide Handles on Clamped Edges

If the top is clamped, hide the top resize handle. Same for the bottom. Showing a handle the user can’t meaningfully interact with is just confusing.

2. Both Edges Clamped: Disable Drag, Keep Tap

When both edges are clamped, the block fills the whole day. Drag-moving it is meaningless — wherever you drop it, it still fills the day. So we disable drag and only allow tap. Tapping opens the event detail screen where dates can be edited directly.

3. One Edge Clamped: Disable Drag-Move, Keep Resize

This was the trickiest case. If you drag-move a partially clamped block, should only the unclamped edge shift? Or the entire event?

Neither felt intuitive. So we disabled drag-move entirely but kept the resize handle on the unclamped edge. If the top is clamped, you can still drag the bottom handle to adjust the end time.

4. Preserve Full Duration on Drag

Even for unclamped blocks, there was a subtle trap. When a block is displayed with clamped coordinates and you apply a drag delta to those clamped values, the original duration gets destroyed.

Say you’re viewing the middle day of a 3-day event. The block is clamped to 0:00-24:00. Apply a 1-hour delta and you get 1:00-25:00 — a 24-hour event instead of the original 48 hours.

The fix is straightforward. The Block type carries clampedStart and clampedEnd alongside the display coordinates. Drag deltas are always applied to the original startDate/endDate. Clamped coordinates are for rendering only.

Visual Feedback: “This Event Continues”

Changing the behavior isn’t enough. Users need to understand why a handle is missing or why drag isn’t working.

We added two visual cues on clamped edges.

1. Flat border-radius

Normal blocks have rounded corners. Clamped edges get a straight cut. It creates the impression that the block extends beyond the screen.

2. 3px color bar

A thin bar in the block’s color appears on the clamped edge. It’s a visual signal: “this event doesn’t end here.” It’s a common pattern in calendar apps.

Together, these tell the user at a glance: “this is a multi-day event, and the top (or bottom) continues on another day.”

Implementation: DraggableTimelineBlock

In the DraggableTimelineBlock component, gesture handlers are configured conditionally based on clamping flags.

// Both clamped → disable drag, tap only
const isDragDisabled = block.clampedTop && block.clampedBottom;

// Either clamped → disable drag-move
const isMoveDisabled = block.clampedTop || block.clampedBottom;

// Resize handles: only show on unclamped edges
const showTopHandle = !block.clampedTop;
const showBottomHandle = !block.clampedBottom;

Applying the drag delta:

// Apply delta to original dates, not clamped coordinates
const newStart = addMinutes(block.clampedStart, deltaMinutes);
const newEnd = addMinutes(block.clampedEnd, deltaMinutes);

This way, moving a 3-day event by 1 hour still results in a 3-day event.

Lessons Learned

  1. Clamping is a display concern. Mutations go to the original data. If you don’t separate screen coordinates from actual dates, you’ll destroy durations.

  2. Hide controls that don’t work. A resize handle that looks interactive but does nothing feels like a bug. Better to remove it.

  3. Disable ambiguous interactions. Drag-moving a partially clamped block has no clear “right” behavior. Rather than guessing, disable it and keep only the unambiguous controls (resize, tap to edit).

  4. Visual cues explain behavioral changes. Without the flat edges and color bars, users would constantly wonder “why can’t I drag this?” The visual treatment answers the question before it’s asked.


Multi-day event dragging seems like a small detail, but clamping, handles, drag behavior, and visual feedback all have to work together consistently. Miss one and the whole interaction feels off.