Skip to content

04core concepts

Constraints

Refuse time on the grid: bounds, per-date rules and per-event vetoes that every gesture honours.

The constraints prop tells the grid what it must refuse: nothing before Monday, no lunch hour, nothing on the 2nd between 4 and 8. Refused time draws as a hatch, drags snap around it, drops into it spring home, and the keyboard says why. Events already sitting in refused time are untouched — constraints restrict gestures, not data.

This one is live — lunch is blocked every day, Wednesday loses 4–8 PM and 9–10 PM, and minDate is midnight today, so yesterday refuses everything. Try dragging Pairing into the hatch, resizing Standup across lunch, or nudging a selected event into a blocked window with the arrow keys:

The four fields

Every field is optional; pass any one and the grid goes active. A ConstraintSlot is a window of time a gesture wants to occupy — start and end as Dates, end exclusive, plus allDay (no relation to the component slots).

proptype · description
minDate?
Date

Nothing may start before this instant. All-day slots compare at day granularity.

maxDate?
Date

Nothing may end after this instant; an all-day slot may run to the end of its day.

isSlotDisabled?
(slot: ConstraintSlot) => boolean

Return true to refuse a window of time. Called with whole spans and with sampled sub-slices to paint the hatch — keep it cheap and pure.

canDrop?
(event: CalEvent, target: ConstraintSlot) => boolean

Whole-drop veto about the event, not the time. Called once with the full target, after the cheap rules pass — never on create.

Note

isSlotDisabled is asked about spans, not points — a moved 90-minute event arrives as one slot. Answer with an overlap test (slot.start < windowEnd && slot.end > windowStart), never by checking where the slot starts.

Blocking hours on one date

Per-date rules are just the callback reading the slot's date. Blocking 4–8 PM and 9–10 PM on one day:

A weekly pattern is the same callback reading the weekday — office hours in four lines:

Availability a week at a time

For availability that lives on a server, fetch it for the visible range with onRangeChange and let the callback read state. The grid re-resolves whenever the constraints object's identity changes, so deriving it from that state is exactly right:

rangePadding widens each fetch so a week of ‹ › steps doesn't wait on the network; the range-fetching example shows the same pattern feeding events.

What is refused, exactly

Every gesture that would put an event somewhere new honours the constraints: drag-create, move, resize, the all-day lane's move and resize, duplicate, paste, and the keyboard forms of each. Clicking a refused empty slot fires no onSlotClick, and double-click quick-create refuses too. During a pointer drag nothing needs to spring back — a refused candidate is simply never accepted, so the eased snap holds the block at the last allowed position.

Two rules keep that from being a trap:

  • The origin exemption. A move or resize is judged only on time it newly occupies. An event already sitting in a blocked window can be dragged out — never deeper — and a shrink is always allowed.
  • Data is exempt. Events you pass in, ICS imports, store writes and the event panel's typed edits are never blocked. If the server says an event exists at 4 PM Wednesday, the calendar draws it there.

The hatch is sampled at snapMinutes, so its edge is exactly where a drag stops; a screen reader gets the blocked periods as a list, and a refused keyboard gesture is announced once, not per keypress.

Timeline gestures

view="timeline" runs through the same KloqConstraints object, unchanged — minDate/maxDate, isSlotDisabled and canDrop all apply, including the origin exemption. It reports its own gesture names rather than reusing the calendar's, in case a canDrop wants to branch on which surface asked:

GestureWhen
"create"Drag-create or double-click-create on empty canvas. No origin exemption — there's no existing event to be exempt.
"lane-move"Dragging a bar, including across rows (a reassignment). Origin-exempt.
"lane-resize"Dragging either edge of a bar. Origin-exempt.

The calendar's own "move" and "resize" never fire on a timeline, and "lane-move"/"lane-resize" never fire on the calendar's time grid.

The pure layer

The same primitives the grid uses are exported for your own surfaces — createConstraints resolves the prop shape, slotAllowed answers for one window, and disabledBands returns the merged refused runs a custom TimeGutter or availability picker can draw: