Reference

API Reference

Everything the package exports, on one page. The other pages explain how the pieces behave; this one says what they are called and what they take.

Entry Points

EntryWhat it holds
@sv5ui/schedulerScheduler, the five views, the sidebar parts, EventChip, dragSource, config and every type
@sv5ui/scheduler/localesThirteen label packs, one export each, so a bundle carries only the ones it imports
@sv5ui/scheduler/theme.cssOne @source line that lets Tailwind see the classes inside the package
// Components
import {
  Scheduler,
  MonthView, WeekView, DayView, YearView, AgendaView,
  EventChip, DateNavigator, CalendarList, SearchBox, DragSourceList
} from '@sv5ui/scheduler';

// Functions and attachments
import { dragSource, defineSchedulerConfig, createTimeScale, mergeLabels } from '@sv5ui/scheduler';

// Types, a selection
import type {
  EventInput, SchedulerEvent, Mutation, EventSourceFn,
  ViewDefinition, LayoutStrategy, InteractionPlugin, StoreMiddleware,
  SchedulerLabels, SchedulerProps
} from '@sv5ui/scheduler';

// Label packs
import { en, vi, ja } from '@sv5ui/scheduler/locales';

Components

ComponentDescription
SchedulerThe whole calendar. Props below
MonthView, WeekView, DayView, YearView, AgendaViewThe five built-in view components, exported to be reused as the component of a ViewDefinition. Each takes ViewProps
EventChipThe default rendering of one event, for use inside an event snippet or on its own
DateNavigatorA month calendar with a dot under every day that has an event; picks a date
CalendarListA checklist that shows and hides calendars; binds hiddenCalendars
SearchBoxA text field for search with a keyboard shortcut
DragSourceListA list of items that create an event where they are dropped

Functions and Attachments

ExportDescription
dragSource(data)Attach to any element so it can be dropped on a scheduler grid
defineSchedulerConfig(config)Slot classes and variants every instance starts from. Replaces the previous config
createTimeScale(options)Wall clock to pixels and back, for a layout or view of your own
mergeLabels(overrides)A complete label set with English filling the gaps

Scheduler Props

SchedulerProps<T>, where T is the shape of event.data and is inferred from the array you bind. Any other attribute goes to the root div. Grouped the way the pages are.

Data and saving

PropTypeDefault
eventsEventInput<T>[]-
sourceEventSourceFn<T>-
onLoadError(error: unknown) => void-
onMutate(m: Mutation<T>) => MutationResult<T> | Promise<...>-
onConflict(m: Mutation<T>, server: SchedulerEvent<T>) => ConflictResolution'keep-server'
onError(m: Mutation<T>, error: unknown) => void-
middlewareStoreMiddleware<T>[][]

Views and navigation

PropTypeDefault
viewstring'week'
dateZonedDateTimetoday
daysnumber-
weekStartsOnWeekDay1
hiddenDaysWeekDay[][]
dayStartHournumber0
dayEndHournumber24
slotMinutesnumber30
slotHeightnumber24
weekNumbersbooleanfalse
toolbarboolean | Snippet<[ToolbarSnippetProps]>true
toolbarActionsSnippet-
onMenu() => void-
viewsViewDefinition<T>[][]
layoutsLayoutStrategy[][]
interactionsInteractionPlugin<T>[][]

Time and language

PropTypeDefault
timeZoneTimeZoneIdlocal zone
localestring'en-US'
hour12booleanfrom locale
labelsPartialLabelsEnglish
dir'ltr' | 'rtl'inherited
businessHoursBusinessHours-
holidaysHoliday[][]

Calendars and filtering

PropTypeDefault
calendarsSchedulerCalendar[][]
hiddenCalendarsstring[][]
searchstring''
filter(event: SchedulerEvent<T>) => boolean-

Interaction and details

PropTypeDefault
creatablebooleantrue
editablebooleantrue
onEventClick(event: SchedulerEvent<T>) => void-
onSelectSlot(selection: SlotSelection) => void-
detail'popover' | 'slideover' | false'popover'
eventDetailSnippet<[EventDetailSnippetProps<T>]>-
eventPanelSnippet<[EventPanelSnippetProps<T>]>-
createPanelSnippet<[CreatePanelSnippetProps<T>]>-
draftSlotSelection | nullnull
dragSourcesDragSourceData<T>[][]

Sidebar

PropTypeDefault
sidebarboolean | Snippet<[SidebarSnippetProps<T>]>false
sidebarHeaderSnippet<[SidebarSnippetProps<T>]>-
sidebarFooterSnippet<[SidebarSnippetProps<T>]>-
sidebarOpenbooleantrue
sidebarSide'start' | 'end''start'
sidebarBreakpointnumber1024

Size and appearance

PropTypeDefault
heightnumber | string-
compactBreakpointnumber640
compactDaysnumber | null3
uiPartial<Record<SchedulerSlots, ClassNameValue>>-
classClassNameValue-
refHTMLElement | nullnull

Rendering snippets

PropTypeDefault
eventSnippet<[EventSnippetProps<T>]>-
cellSnippet<[CellSnippetProps]>-
headerSnippet<[HeaderSnippetProps]>-
emptySnippet<[EmptySnippetProps]>-

Snippet Arguments

What each snippet receives. Every argument type is exported so a snippet declared outside the component can be typed.

TypeFields
EventSnippetPropsevent, position, view, isDragging, isResizing, isSelected
CellSnippetPropsdate, view, isToday, isAnchor, isWeekend, isHoliday, isBusinessHours, isOutside
HeaderSnippetPropsdate, view, label, isToday, isAnchor, holiday
EmptySnippetPropsview, range
EventDetailSnippetPropsevent, close
EventPanelSnippetPropsevent, close, remove, update, deletable
CreatePanelSnippetPropsstart, end, allDay, close, create
ToolbarSnippetPropstitle, date, range, view, views, scheduler, step, today, navigate, setView, toggleSidebar, sidebarOpen, compact
SidebarSnippetPropsdate, view, range, events, scheduler, navigate, close, docked

Building Block Props

The props of the five standalone components, in short. The Building Blocks page has the full tables.

TypeProps
EventChipPropsevent, position, locale, hour12, color, size, variant, showTime, selected, dragging, ui, class, children, ref
DateNavigatorPropsdate, onSelect, events, timeZone, locale, weekStartsOn, labels, ui, class, ref
CalendarListPropscalendars, hiddenCalendars, title, labels, item, ui, class, ref
CalendarListItemPropscalendar, visible, toggle
SearchBoxPropsvalue, shortcut, labels, ui, class, ref, plus HTML input attributes
DragSourceListPropsitems, calendars, title, labels, item, ui, class, ref
DragSourceListItemPropsitem, color

Types

Sixty five types, grouped by what they describe. The listings below are trimmed to their fields; JSDoc on every field is in the package.

GroupTypes
EventsSchedulerEvent, EventInput, NewEventInput, EventChanges, EventColor, SchedulerCalendar, SchedulerResource
RangesDateInput, DateRange, TimeZoneId, WeekDay, BusinessHours, Holiday
RecurrenceRecurrenceRule, RecurrenceRuleInput, RecurrenceFrequency, OrdinalWeekDay
SavingMutation, MutationKind, MutationResult, MutationHandlers, ConflictResolution, EventPatch, StoreMiddleware
LoadingEventSourceFn, LoadContext
ViewsViewDefinition, ViewProps, ViewSnippets, ViewPreview, SchedulerContext
LayoutLayoutStrategy, LayoutContext, PositionedEvent, TimePosition, SpanPosition, TimeScale, TimeScaleOptions
InteractionInteractionPlugin, InteractionContext, InteractionPreview, HitTarget, GridFocus, SlotSelection, DragSourceData
Labels and configSchedulerLabels, PartialLabels, SchedulerConfig
Component propsSchedulerProps, EventChipProps, DateNavigatorProps, CalendarListProps, CalendarListItemProps, SearchBoxProps, DragSourceListProps, DragSourceListItemProps

Events

// What the scheduler holds. Times are always ZonedDateTime; end is exclusive.
interface SchedulerEvent<T = unknown> {
  id: string;                    // stable; mutations are keyed by it
  title: string;
  start: ZonedDateTime;
  end: ZonedDateTime;
  allDay?: boolean;              // all-day row and month cells rather than the time grid
  calendarId?: string;           // joins one of `calendars`; supplies the colour
  color?: EventColor;            // overrides the calendar colour
  background?: boolean;          // drawn behind other events, never editable
  editable?: boolean;            // false locks this event
  recurrence?: RecurrenceRule;   // makes the event a series
  seriesId?: string;             // set on an occurrence: the id of its series
  resourceId?: string;           // stored and returned; no built-in view reads it
  data?: T;                      // your payload, never read by the scheduler
}

// What you pass in: the same, with dates as strings or ZonedDateTime
type EventInput<T> = Omit<SchedulerEvent<T>, 'start' | 'end' | 'recurrence'> & {
  start: DateInput; end: DateInput; recurrence?: RecurrenceRuleInput;
};
type DateInput = string | ZonedDateTime;

type NewEventInput<T> = Omit<EventInput<T>, 'id'> & { id?: string };   // createPanel.create
type EventChanges<T> = Partial<Omit<EventInput<T>, 'id'>>;            // eventPanel.update

type EventColor = 'primary' | 'secondary' | 'tertiary' | 'success'
                | 'warning' | 'error' | 'info' | 'surface';

interface SchedulerCalendar { id: string; title: string; color?: EventColor }
interface SchedulerResource { id: string; title: string; parentId?: string; capacity?: number; order?: number }

Ranges, Hours and Holidays

type TimeZoneId = string;                       // IANA, e.g. 'Asia/Ho_Chi_Minh'
type WeekDay = 0 | 1 | 2 | 3 | 4 | 5 | 6;       // 0 is Sunday, as Date.getDay()
interface DateRange { start: ZonedDateTime; end: ZonedDateTime }   // [start, end)

interface BusinessHours {
  start: string;      // 'HH:mm'
  end: string;        // 'HH:mm', exclusive
  days: WeekDay[];
}

interface Holiday {
  date: string;       // 'YYYY-MM-DD', read in the scheduler's timeZone
  title?: string;
}

Recurrence

type RecurrenceFrequency = 'daily' | 'weekly' | 'monthly' | 'yearly';

interface RecurrenceRule {
  freq: RecurrenceFrequency;
  interval?: number;                       // default 1
  byDay?: Array<WeekDay | OrdinalWeekDay>; // plain weekdays are evaluated; ordinals are not
  count?: number;                          // wins over until when both are set
  until?: ZonedDateTime;                   // latest start, inclusive
  exDates?: ZonedDateTime[];               // occurrences to drop, matched by instant
  weekStart?: WeekDay;                     // for weekly rules with interval > 1
  // Accepted so stored data keeps its shape, not evaluated, one dev warning per series:
  rDates?: ZonedDateTime[]; bySetPos?: number[]; byMonthDay?: number[];
  byMonth?: number[]; byWeekNo?: number[]; byYearDay?: number[];
}

interface OrdinalWeekDay { day: WeekDay; ordinal: number }   // -1 is the last

// The input form takes DateInput for until, exDates and rDates
type RecurrenceRuleInput = Omit<RecurrenceRule, 'until' | 'exDates' | 'rDates'> & {
  until?: DateInput; exDates?: DateInput[]; rDates?: DateInput[];
};

Saving

type MutationKind = 'create' | 'update' | 'delete' | 'move' | 'resize';

interface Mutation<T = unknown> {
  id: string;                        // unique per mutation, assigned by the scheduler
  kind: MutationKind;
  eventId: string;
  before: SchedulerEvent<T> | null;  // null on create
  after: SchedulerEvent<T> | null;   // null on delete
}

type MutationResult<T> = void | EventInput<T>;   // return the server's version to compare
type ConflictResolution = 'keep-server' | 'keep-local';

interface MutationHandlers<T> {
  onMutate?: (mutation: Mutation<T>) => MutationResult<T> | Promise<MutationResult<T>>;
  onConflict?: (mutation: Mutation<T>, server: SchedulerEvent<T>) => ConflictResolution;
  onError?: (mutation: Mutation<T>, error: unknown) => void;
}

// What reaches the store, and what middleware wraps
type EventPatch<T> =
  | { type: 'upsert'; event: SchedulerEvent<T> }
  | { type: 'remove'; eventId: string }
  | { type: 'reset'; events: SchedulerEvent<T>[] };

type StoreMiddleware<T> = (next: (patch: EventPatch<T>) => void) => (patch: EventPatch<T>) => void;

Loading

interface LoadContext {
  range: DateRange;      // return every event that overlaps it
  timeZone: TimeZoneId;
  signal: AbortSignal;   // aborted when the range changes before this load settles
}

type EventSourceFn<T = unknown> = (context: LoadContext) => EventInput<T>[] | Promise<EventInput<T>[]>;

Views

interface ViewDefinition<T = unknown> {
  name: string;                 // the value of `view` that activates it
  label?: string;               // switcher text; built-in views take theirs from labels
  layout: string;               // 'time-grid' | 'month-grid' | 'list' | a registered LayoutStrategy
  columnsPerRow?: number;       // the month view uses 7
  range: (anchor: ZonedDateTime, context: SchedulerContext) => DateRange;
  step: (anchor: ZonedDateTime, direction: 1 | -1, context: SchedulerContext) => ZonedDateTime;
  title?: (anchor: ZonedDateTime, range: DateRange, context: SchedulerContext) => string;
  shortTitle?: (anchor: ZonedDateTime, range: DateRange, context: SchedulerContext) => string;
  component: Component<ViewProps<T>>;
}

// Everything a view component receives. It reads no context of its own.
interface ViewProps<T = unknown> {
  view: string; anchor: ZonedDateTime; range: DateRange;
  days: ZonedDateTime[];            // one per column, hiddenDays already removed
  columnsPerRow: number;
  events: SchedulerEvent<T>[];      // filtered and expanded for the range
  scheduler: SchedulerContext;
  scale: TimeScale;
  positioned: PositionedEvent<T>[]; // what the layout produced
  snippets: ViewSnippets<T>;        // { event?, cell?, header?, empty?, detail? }
  focus: GridFocus | null;
  interactions: { grid: Attachment<HTMLElement>; event: (position: PositionedEvent<T>) => Attachment<HTMLElement> };
  preview: ViewPreview<T> | null;   // InteractionPreview plus its positioned placements
  selectedEventId: string | null;
  loading: boolean;                 // true while source fetches the range
  detailPopover: boolean;
  onSelectEvent: (eventId: string | null) => void;
  onDeleteEvent: (eventId: string) => void;
  onOpenEvent?: (eventId: string) => void;
  navigate: (date: ZonedDateTime, view?: string) => void;
}

// Read-only settings shared by views, layouts and interactions
interface SchedulerContext {
  timeZone: TimeZoneId; locale: string; weekStartsOn: WeekDay; hour12?: boolean;
  businessHours?: BusinessHours; holidays: Holiday[]; calendars: SchedulerCalendar[];
  dayCount?: number; weekNumbers: boolean; hiddenDays: WeekDay[];
  editable: boolean; direction: 'ltr' | 'rtl'; compact: boolean;
  labels: SchedulerLabels;
  now: ZonedDateTime;   // refreshed once a minute
}

Layout

interface LayoutStrategy {
  name: string;
  layout<T>(events: SchedulerEvent<T>[], range: DateRange, context: LayoutContext): PositionedEvent<T>[];
}

interface LayoutContext { scale: TimeScale; days: ZonedDateTime[]; columnsPerRow: number; scheduler: SchedulerContext }

type PositionedEvent<T> = TimePosition<T> | SpanPosition<T>;

interface TimePosition<T> {          // one segment in one day column of the time grid
  kind: 'time'; event: SchedulerEvent<T>; dayIndex: number;
  segmentStart: ZonedDateTime; segmentEnd: ZonedDateTime;
  top: number; height: number;       // pixels, from TimeScale
  left: number; width: number;       // fractions of the column width
  column: number; columns: number;   // place inside the overlap cluster
}

interface SpanPosition<T> {          // one segment across whole-day cells
  kind: 'span'; event: SchedulerEvent<T>;
  row: number; lane: number; startColumn: number; endColumn: number;   // endColumn exclusive
  continuesBefore: boolean; continuesAfter: boolean;
}

// Wall clock to pixels and back; the same instance is shared by the view, its layout and the gestures
interface TimeScale {
  readonly slotMinutes: number; readonly slotHeight: number;
  readonly startHour: number; readonly endHour: number; readonly dayHeight: number;
  toPixel(date: ZonedDateTime): number;
  toDate(pixel: number, dayStart: ZonedDateTime): ZonedDateTime;
}

interface TimeScaleOptions { slotMinutes?: number; slotHeight?: number; startHour?: number; endHour?: number }
function createTimeScale(options?: TimeScaleOptions): TimeScale;

Interaction

interface InteractionPlugin<T = unknown> {
  name: string;   // a built-in name (create, move, resize, keyboard, external) replaces that plugin
  attach: (context: InteractionContext<T>) => Attachment<HTMLElement>;
  attachEvent?: (context: InteractionContext<T>, position: PositionedEvent<T>) => Attachment<HTMLElement>;
}

interface InteractionContext<T = unknown> {
  // read the grid
  view: string; viewLabel: string; range: DateRange; days: ZonedDateTime[]; columnsPerRow: number;
  scale: TimeScale; scheduler: SchedulerContext;
  events: readonly SchedulerEvent<T>[];           // filtered and expanded for the range
  getEvent: (eventId: string) => SchedulerEvent<T> | undefined;
  hitTest: (clientX: number, clientY: number) => HitTarget | null;
  snap: (date: ZonedDateTime) => ZonedDateTime;
  // state
  selectedEventId: string | null; select: (eventId: string | null) => void;
  focus: GridFocus | null; setFocus: (focus: GridFocus | null) => void;
  setPreview: (preview: InteractionPreview<T> | null) => void;
  // act
  commit: (mutation: Omit<Mutation<T>, 'id'>) => void;   // through the save pipeline
  selectSlot: (point: Pick<HitTarget, 'date' | 'allDay'>) => void;   // reports one slot
  selectRange: (selection: SlotSelection) => void;                    // reports and opens createPanel
  newEventId: () => string;
  step: (direction: 1 | -1) => void;
  navigate: (date: ZonedDateTime, view?: string) => void;
  announce: (message: string) => void;
}

interface HitTarget { date: ZonedDateTime; dayIndex: number; allDay: boolean; keepsTime: boolean; eventId: string | null }
interface GridFocus { dayIndex: number; minutes: number | null }     // null minutes is a whole-day cell
interface SlotSelection { start: ZonedDateTime; end: ZonedDateTime; allDay: boolean }
interface InteractionPreview<T> { kind: 'create' | 'move' | 'resize'; event: SchedulerEvent<T> }

// What a dragSource element carries; the drop fills in start, end and a generated id
type DragSourceData<T> = Omit<EventInput<T>, 'id' | 'start' | 'end' | 'recurrence'>
  & { id?: string; durationMinutes?: number };

function dragSource<T>(data: () => DragSourceData<T>): Attachment<HTMLElement>;

Labels and Config

// Every string the scheduler shows or announces. The full list is on the Localization page.
interface SchedulerLabels { today: string; /* ... */ more: (count: number) => string; announce: { /* ... */ } }

// Any subset; missing keys fall back to English
type PartialLabels = Partial<Omit<SchedulerLabels, 'announce'>> & { announce?: Partial<SchedulerLabels['announce']> };
function mergeLabels(overrides?: PartialLabels): SchedulerLabels;

// App-wide defaults, keyed by component name
type SchedulerConfig = Record<string, { defaultVariants?: Record<string, unknown>; slots?: Record<string, string> } | undefined>;
function defineSchedulerConfig(config: SchedulerConfig): void;

Locales

Each pack is a complete SchedulerLabels, exported from @sv5ui/scheduler/locales.

ExportLanguage
arArabic
deGerman
enEnglish (the default)
esSpanish
frFrench
itItalian
jaJapanese
koKorean
nlDutch
ptPortuguese
ruRussian
viVietnamese
zhChinese

Stability

Everything on this page is public. The package is in its 0.x line, where a minor release may change an export; the changelog names each such change. Internals stay unexported: the layout maths, the event store, the mutation pipeline and the gesture engine change freely. The data-sch-* attributes listed on the Theming page are public too.