> ## Documentation Index
> Fetch the complete documentation index at: https://super-calendar.afonsojramos.me/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.

All of this lives on the week/day grid and is opt-in: pass the relevant handler
and update your own event state in response.

## 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)))
  }
/>
```

### 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

`onCreateEvent` also works in `month` mode (web): press a 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). A plain click still fires `onPressDay`,
and each day in the sweep carries `data-creating` for styling (see
[Styling](/guides/styling)).

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

## Tuning the snap

`dragStepMinutes` (default 15) controls how move, resize, and create snap. Set it
to `5`, `30`, etc. to match your grid's granularity.
