Skip to content

Availability, blocked times and buffers

Calendar can preview and reject unavailable intervals before a consumer sends a change to its API. The rules are shared by Vanilla, React and Vue. This is not a booking backend: the server must still validate every write against current data.

Minimal example

ts
import { createCalendar } from '@xsigns/chronosketch'

const calendar = createCalendar({
    date: '2026-09-07',
    view: 'week',
    preventEventOverlap: true,
    events: [{
        id: 'workshop', title: 'Workshop', kind: 'timed',
        start: '2026-09-07T09:00', end: '2026-09-07T10:00',
        protection: { afterMinutes: 30 },
    }],
    constraints: [{
        id: 'maintenance', label: 'Maintenance', kind: 'timed',
        start: '2026-09-07T12:00', end: '2026-09-07T13:00',
    }],
    onEventCreateRequested: range => openBookingForm(range),
})

10:00–10:30 is the workshop's buffer; a new event may start at 10:30. 12:00–13:00 is explicitly blocked. Other intervals remain selectable, including other times on the same day. openBookingForm is your application callback.

Data contracts

CalendarConstraint combines an explicit kind: 'timed' | 'all-day' interval with a nonempty unique id, optional human-readable label, optional blocks: ('create' | 'move' | 'resize')[], and optional owning eventId. Omitting blocks blocks all three actions; [] blocks none and is not rendered. An owned constraint is ignored when moving/resizing that event, not when creating another event. Ownership is useful for imported busy intervals; fixed constraints do not move automatically. Use event protection for buffers that should move.

EventProtection has optional beforeMinutes and afterMinutes. Values must be non-negative safe integers. Defaults are zero. These are generic library controls, not a requirement to show two inputs in your product. For one pause between bookings, provide only afterMinutes; do not duplicate it on both sides. If both adjacent events have buffers, their protected intervals may not overlap; a preceding after-buffer and following before-buffer therefore add up.

Intervals are half-open [start, end): touching boundaries do not overlap. All-day boundaries are interpreted as floating midnight; timed intervals use the existing YYYY-MM-DDTHH:mm contract. Buffers can cross midnight and month/year boundaries. They must remain inside the supported date range. There is no timezone conversion, recurrence expansion, DST resolution or instant-based arithmetic.

preventEventOverlap defaults to false, preserving overlapping calendar use cases. When true, every other supplied event blocks its protected interval. The event being edited is excluded. Buffer bands are displayed when this option is enabled. Explicit constraints apply independently of this option, including against the candidate's protection.

Checking and updating

ts
const result = calendar.checkInteraction({
    action: 'create',
    candidate: {
        kind: 'timed', start: '2026-09-07T10:30', end: '2026-09-07T11:30',
        protection: { afterMinutes: 15 },
    },
})
if (!result.allowed) showConflicts(result.conflicts, result.reason)

calendar.setConstraints(nextConstraints) // undefined resets to []
calendar.setPreventEventOverlap(true)   // undefined resets to false
calendar.setInteractionValidator(request => {
    return withinOpeningHours(request.candidate) ? undefined : 'Outside opening hours'
})

result contains allowed, immutable conflicts with id, optional label and source: 'constraint' | 'event', plus an optional custom reason. For an edit, provide previous with the original event ID to exclude itself and its owned constraints. getConstraints() returns the immutable current constraints; getPreventEventOverlap() returns the current overlap policy.

validateInteraction / setInteractionValidator accepts a pure synchronous callback. Return undefined to allow or a nonempty localized string to reject. It runs only after built-in checks succeed, possibly repeatedly during a gesture; do not fetch data, mutate state or perform side effects inside it. No async result is supported. Custom rules do not automatically produce visual background bands: provide concrete constraints for times that should be visible in advance.

Programmatic moveEvent, moveTimedEvent and resizeTimedEvent use the same checks and throw CalendarAvailabilityError on a conflict, before changing state or notifying subscribers. Inspect its result. Invalid data still throws normal validation errors. setEvents is an authoritative import, not a booking command: it can import conflicting data. Creation requests do not persist anything.

Interaction and presentation

  • Week/Day: hatched minute-positioned blocked and buffer bands behind events.
  • Month/Agenda and the all-day header: expandable per-day interval summaries, including constraint-only days in Agenda. Native summary controls support Tab, Enter and Space; a partial-day block never disables the entire date.
  • Month/all-day dragging, timed move/resize and supported drawing gestures show a dashed rejection outline and a localized live status message for conflicts. Releasing an invalid change does not update the event or call a success callback.
  • Existing cancellation behavior (Escape, pointer cancellation, disposal) remains. Updating constraints refreshes the view and disposes any active gesture.
  • Date navigation/click callbacks still select dates; they are not booking commands.

The existing gesture scope is unchanged: drawing is available in Month and Week; timed move/resize is mouse-only for supported single-day events in Week/Day. Touch and keyboard editing are not introduced by availability support.

Drawing supplies an interval without product-specific protection. If new bookings need a default buffer, include it in your pure custom validation rule and validate the final form candidate with protection before persisting. Backend rejection can still occur because of concurrent changes or incomplete client data; retain a clear consumer error/refresh flow.

Frameworks, resources and loading

React uses the same options as props; Vue uses its options prop. Both synchronize constraints, overlap policy and validator changes. Vanilla uses the setters above. All types and CalendarAvailabilityError are exported from the root and /core.

Calendar has no resource lanes: scope events and constraints to the selected resource(s) in your application. For cross-resource rules use a custom validator with the relevant preloaded data. This release does not add availability to the separate resource Timeline. There are no resource/provider/product identifiers embedded in the constraint model.

Supply all intervals that can conflict, including buffers extending into the visible range from adjacent dates. Client checks scan supplied data; they do not load availability or guarantee completeness. Recurring opening hours, shared capacity and remote availability sources remain consumer responsibilities.

Try the opt-in playground at /?availability=true&framework=vanilla, react or vue. It uses sample events only and does not send booking requests.

See styling for colors and styling hooks.

ChronoSketch · Calendar and scheduling components