FullScheduler - Calendar - Gallery Image

FullScheduler — Full Guide - Drag & Resize Calendar Events

Docs, demos, API

Drag & ResizeInteractions
Callbacks with API (v1.0.35)setThreshold (snap)Per-instance isolation (v1.0.36)

Learn how to enable drag and resize, how to validate/guard these actions, and how to finalize them safely using accept(), decline() or abort(). Since v1.0.35 every callback receives the api as the second argument (payload, api).

Interactive demoLive

Move events (drag) and resize edges. The panel on the right logs both actions. Guard-rails include cross-day toggle, minimum duration and optional clamping to business hours.

Minimal wiring

Turn on interactions and finalize the change with accept(). Rule: if you call accept(), do not call updateEvents(...) for the same interaction — the scheduler already applied times.

TypeScript
import type { FullSchedulerApiInitOptions, FullSchedulerDrop, FullSchedulerResize } from 'fullscheduler'

const options: FullSchedulerApiInitOptions<HappyEvent> = {
  draggableEvents: true,
  resizableEvents: true,

  onEventDropped: (d) => {
    d.accept() // ✅ scheduler applies new start/end for this drop
  },

  onEventStartResized: (r) => {
    // run quick validation...
    r.accept?.() // ✅ scheduler applies new start
  },

  onEventEndResized: (r) => {
    // run quick validation...
    r.accept?.() // ✅ scheduler applies new end
  }
}
Callbacks cheatsheet (accept / decline / abort)
TypeScript
import type { FullSchedulerDrop, FullSchedulerResize, FullSchedulerUserApi } from 'fullscheduler'

// (payload, api) — since v1.0.35
onEventDropped: (d: FullSchedulerDrop<HappyEvent>, api: FullSchedulerUserApi<HappyEvent>) => {
  d.accept()
  // B) Or abort: d.abort?.()
},

onEventStartResized: (r: FullSchedulerResize<HappyEvent>, api: FullSchedulerUserApi<HappyEvent>) => {
  // If valid → accept(); otherwise decline()/abort()
  // r.accept?.()
  // r.decline?.()
  // r.abort?.()
},

onEventEndResized: (r: FullSchedulerResize<HappyEvent>, api: FullSchedulerUserApi<HappyEvent>) => {
  // Example of reject-then-patch scenario:
  // r.decline?.()
}
Common guard-rails

Cross-day & minimum duration:

TypeScript
import type { FullSchedulerResize, FullSchedulerUserApi } from 'fullscheduler'

const startOfDay = (d: Date) => new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0,0,0,0)
const sameDay = (a: Date, b: Date) => startOfDay(a).getTime() === startOfDay(b).getTime()
const minutes = (ms: number) => Math.round(ms / 60000)

const allowCrossDay = false
const minDuration = 30, m = 60000

onEventEndResized: (r: FullSchedulerResize<HappyEvent>, api: FullSchedulerUserApi<HappyEvent>) => {
  const candidate = { start: r.event.start, end: r.newDateTime }

  if (!allowCrossDay && !sameDay(candidate.start, candidate.end)) {
    r.decline?.()
    return // ❌ cross-day not allowed
  }

  const dur = minutes(candidate.end.getTime() - candidate.start.getTime())
  if (dur < minDuration) {
    r.decline?.()
    return // ❌ too short
  }

  r.accept?.() // ✅ valid → let scheduler apply
}

Clamp to business hours (decline + patch):

TypeScript
import type { FullSchedulerResize, FullSchedulerUserApi } from 'fullscheduler'

const clampToBusiness = (d: Date) => {
  const s = 8, e = 18
  const c = new Date(d)
  if (c.getHours() < s) c.setHours(s, 0, 0, 0)
  if (c.getHours() > e) c.setHours(e, 0, 0, 0)
  return c
}

onEventStartResized: (r: FullSchedulerResize<HappyEvent>, api: FullSchedulerUserApi<HappyEvent>) => {
  const clamped = clampToBusiness(r.newDateTime)
  if (clamped.getTime() !== r.newDateTime.getTime()) {
    r.decline?.()
    return
  }
  r.accept?.()
}
Snap threshold (minutes)

Configure snapping to the time grid. You can set it at init (option) or adjust at runtime with api.setThreshold(...).

TypeScript
import type { FullSchedulerApiInitOptions, FullSchedulerUserApi } from 'fullscheduler'

// At init:
const options: FullSchedulerApiInitOptions<HappyEvent> = {
  draggableEvents: true,
  resizableEvents: true,
  threshold: 15, // minutes
  onFullSchedulerApiReady: (api: FullSchedulerUserApi<HappyEvent>) => {
    // Or adjust later at runtime:
    api.setThreshold?.(15)
  }
}
Best practices
  • One or the other: accept() or decline()/abort() + API patch — never both.
  • Keep guard-rails small and focused; avoid heavy logic inside callbacks.
  • For clamping/normalization, prefer decline() and then patch a clean value via api.updateEvents.
  • Use setThreshold to match your grid (e.g., 5/10/15 minutes).
  • Renderer is optional for interactions, but helps present actions (icons, buttons, hints).