> ## Documentation Index
> Fetch the complete documentation index at: https://afonsojramos-claude-issues-54-55-0b0zaz.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Drag, resize & create events

> Move, resize, reschedule across days, and create events by dragging.

Everything here is opt-in: pass the relevant handler and update your own event
state in response. Move, resize, and create work on the week/day grid; move and
create also work on the month grid (see [On the month grid](#on-the-month-grid)),
where a drag moves an event by whole days rather than by time. The year grid
sweeps out day ranges too (see [On the year grid](#on-the-year-grid)).

## Move and resize

Pass `onDragEvent` to make events draggable. Move an event (**long-press** it on
native, **click-drag** it on web), or **drag the grip** at its **bottom edge to
change the end** or its **top edge to change the start**. Drag **vertically to
change the time, horizontally to move it to another day** (within the visible
range). New `start`/`end` are snapped to `dragStepMinutes` (default 15).

```tsx theme={null}
<Calendar
  /* ... */
  onDragEvent={(event, start, end) =>
    setEvents((prev) => prev.map((e) => (e.id === event.id ? { ...e, start, end } : e)))
  }
/>
```

### Move an event past midnight

A move keeps its duration, so dragging an event **down past the end of the day**
lets its end run into the next one: the box cuts off at the day boundary and the
remainder previews at the top of the next column, exactly where the committed
event will render. The **start** stays in the day you dropped it on (it stops one
`dragStepMinutes` step before the end of the day), so a move to a different day is
still the horizontal drag.

`onDragEvent` then receives a `start` and `end` on different dates. Dragging any
day's segment of the resulting multi-day event moves the whole thing, so it never
gets clipped to the day you grabbed. All visible segments preview the move together,
including segments that enter a new column. Both dates shift together to preserve
the event's duration. For example, moving a Tuesday 17:00 to Thursday 21:00 event
one day earlier previews Monday 17:00 to Wednesday 21:00, whichever segment you grab.
Use a resize grip to change just the start or end.

### Hide the drag handle

By default a small grip shows at each draggable event's bottom edge. Set
`showDragHandle={false}` to hide that indicator while keeping drag-to-move and
drag-to-resize fully working, so events stay editable without the visual marker.

```tsx theme={null}
<Calendar
  /* ... */
  showDragHandle={false}
  onDragEvent={(event, start, end) =>
    setEvents((prev) => prev.map((e) => (e.id === event.id ? { ...e, start, end } : e)))
  }
/>
```

### Lock specific events

`onDragEvent` makes **every** event draggable, which is the right default: most
calendars let you reschedule anything. When a few events must never move (a
confirmed booking, an event someone else owns, a holiday), set `draggable: false`
on those events. They keep their normal appearance and still respond to taps, they
just can't be picked up or resized. Nothing else needs to change.

```tsx theme={null}
const events = [
  { id: "1", title: "Standup", start, end },
  { id: "2", title: "Driving test", start, end, draggable: false }, // can't be moved
];
```

Reach for this when an event can *never* move, so you don't offer a drag that only
snaps back. To refuse a move for some targets but not others (see below), keep the
event draggable and reject the specific drop instead. (For an event that shouldn't
respond to taps either and should read as unavailable, use `disabled: true`, which
also dims it.)

### Allow only move or only resize

Split the two with `startEditable` (can be moved) and `durationEditable` (can be
resized), per event or grid-wide via `eventStartEditable` / `eventDurationEditable`
(both default `true`). A `draggable: false` event stays fully locked regardless.

```tsx theme={null}
const events = [
  { id: "1", title: "Standup", start, end, durationEditable: false }, // move, don't resize
  { id: "2", title: "Focus", start, end, startEditable: false }, // resize, don't move
];

// Or a grid-wide default (e.g. resize is off everywhere):
<Calendar eventDurationEditable={false} onDragEvent={onDrag} /* ... */ />;
```

### Reject a drop

Return `false` from `onDragEvent` to refuse a particular placement — the event
snaps back to where it started (on the time grid a cross-week drop snaps the view
back too, so the rejection is visible). Use it for rules that depend on where the
event lands: overlaps, out-of-bounds slots, or business-hours limits. The library
is expo-free, so fire your own feedback (a haptic, a toast) right where you reject.

```tsx theme={null}
import * as Haptics from "expo-haptics";

onDragEvent={(event, start, end) => {
  if (overlapsAnother(event, start, end)) {
    void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
    return false;
  }
  setEvents((prev) => prev.map((e) => (e.id === event.id ? { ...e, start, end } : e)));
}}
```

The overlap case is common enough that it's built in: set `eventOverlap={false}` and
the grid rejects any drag or resize that would land an event on top of another,
without you checking in `onDragEvent`. For your own rules, `eventsOverlap` and
`overlapsOtherEvents` are exported from `@super-calendar/core`.

```tsx theme={null}
<Calendar eventOverlap={false} onDragEvent={onDrag} /* ... */ />
```

### Haptics on grab

`onDragStart` fires the instant an event is picked up for a move or resize,
before anything is committed. The library is expo-free, so bring your own
haptics:

```tsx theme={null}
import * as Haptics from "expo-haptics";

<Calendar
  /* ... */
  onDragStart={() => {
    void Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
  }}
/>;
```

### Move an event to another week

While moving an event, drag it past the left or right edge of the day columns and
hold briefly. The view pages to the previous/next period and the event lifts into
a floating copy that keeps following your finger, so the drag stays live across the
page change and you drop it on any day of the newly revealed week. Both renderers
need `onChangeDate` set (they already do for paging); the page only advances on a
deliberate dwell at the very edge, so a normal in-view drag never trips it.

On native the pager's own swipe is frozen for the moment it pages, so the view
advances under your held finger instead of waiting for you to lift.

In-view move, cross-day within the visible columns, and resize all work by gesture
as usual on both.

### Screen-reader rescheduling

Dragging is a gesture, so on the React Native renderer draggable events also carry
**accessibility actions** for VoiceOver / TalkBack users: *move earlier*, *move
later*, (when the event owns its end) *extend* / *shorten*, and *move to next /
previous week* (per-mode: the page span), each stepping by `dragStepMinutes` or a
whole page. They run through the same commit path as a drag, so `onDragEvent`
fires exactly as it would from a gesture (and can still return `false` to reject).
The built-in event renderer wires these up automatically; a custom `renderEvent`
should spread the `accessibilityActions` and `onAccessibilityAction` it receives
onto its pressable to keep the event operable by assistive tech.

## Drag to create

Pass `onCreateEvent` to sweep out a new event on empty grid space:
**long-press and drag** on native, **click-drag** on web. The handler receives
the snapped `start`/`end` on release (a stationary press yields a one-step
range). On native it supersedes `onLongPressCell`; on web, dragging empty space
creates instead of scrolling (use the wheel to scroll), and **Escape** cancels an
in-progress sweep before it commits.

```tsx theme={null}
<Calendar
  /* ... */
  onCreateEvent={(start, end) =>
    setEvents((prev) => [...prev, { id: makeId(), title: "New event", start, end }])
  }
/>
```

<Note>
  A live ghost box previews the range as you sweep. Tap (no drag) on empty space still fires
  `onPressCell` with the pressed date+time, so you can support both "tap to create a point" and
  "drag to create a range."
</Note>

## On the month grid

The same two handlers work in `month` mode on both renderers. Nothing extra to
enable: pass `onCreateEvent`, `onDragEvent`, or both.

### Drag to create a day span

Press an empty day and drag across others to sketch an **all-day** span, then
release. `start` is midnight of the first day and `end` is midnight after the last
(exclusive). On native you **long-press** first, so a tap and a page swipe are
never hijacked, and a stationary hold creates that single day; on web a plain
click without dragging creates nothing and still fires `onPressDay`.

```tsx theme={null}
<Calendar
  mode="month"
  /* ... */
  onCreateEvent={(start, end) =>
    setEvents((prev) => [...prev, { id: makeId(), title: "New", start, end, allDay: true }])
  }
/>
```

`onSelectDrag` reports the **same sweep** as it happens, with the ordered inclusive
`[start, end]` days, so a selection highlight can follow the drag rather than
appearing only on release. Set it, `onCreateEvent`, or both; either enables the
gesture. See [Date selection](/guides/selection) for wiring it to `useDateRange`.

### Drag to reschedule

Pick an event bar up and drop it on another day. Both ends shift by the same
number of calendar days, so the **time of day and the duration are preserved**
(a multi-day event keeps its length). Grab it with a **long-press** on native and
a **press-drag** on web. The carried bar fades and the target day is tinted while
you drag.

```tsx theme={null}
<Calendar
  mode="month"
  /* ... */
  onDragEvent={(event, start, end) =>
    setEvents((prev) => prev.map((e) => (e.id === event.id ? { ...e, start, end } : e)))
  }
/>
```

Everything above applies here too: `draggable: false` and `startEditable: false`
lock an event, `eventStartEditable={false}` locks the whole grid, returning
`false` rejects a drop, and `eventOverlap={false}` rejects one that would land on
top of another event. `onDragStart` fires on grab, for haptics. There is no resize
on the month grid, so `durationEditable` has no effect there.

### Styling the drag states

Both states are tinted with the theme's `rangeBackground` token by default (see
[Theming](/guides/theming)), and the bar being carried fades. On the dom renderer
each day in a create sweep also carries `data-creating` and the day an event is
about to land on carries `data-drop`, so you can restyle either (see
[Styling](/guides/styling)); give the `day` slot a class to take the tint over
entirely.

## On the year grid

The twelve mini months take the same sweep: `onSelectDrag` reports the ordered
inclusive `[start, end]` days as you drag, and `onCreateEvent` reports the
all-day range once on release. Hold a day and drag on a device, press and drag on
the web. Days being swept carry `data-creating` on the dom renderer.

```tsx theme={null}
<Calendar
  mode="year"
  /* ... */
  selectedRange={range ?? undefined}
  onSelectDrag={selectRange}
  onCreateEvent={(start, end) =>
    setEvents((prev) => [...prev, { id: makeId(), title: "Leave", start, end, allDay: true }])
  }
/>
```

On a device a sweep stays inside the mini month it started in; on the web it can
carry on across months. The year view summarises events as dots rather than bars,
so it has nothing to pick up: `onDragEvent` applies to `month` mode, not `year`.

## Tuning the snap

`dragStepMinutes` (default 15) controls how move, resize, and create snap on the
week/day grid. The month and year grids always snap to whole days.
