sdxc

Type to search, or start from one of these:

[ @sdxc/ui/behaviors ]

CalendarModel

Headless state model for calendar grids.

import { CalendarModel } from "@sdxc/ui/behaviors";

Usage

Owns the ephemeral state around picking a date: the keyboard-focused day, the visible month page, and a range's anchor plus hover preview. The chosen value stays with the consumer, and every state change dispatches "change".

Signature

new CalendarModel(options?: CalendarModel.Options)

Parameters

ParameterTypeDescription
options?CalendarModel.OptionsInitial focus, selectable bounds, and disabled-day rule.

Properties

PropertyTypeDescription
focusedDateDateThe day that currently carries keyboard/roving-tabindex focus. Always a fresh Date instance, so callers may mutate it freely.
visibleMonthDateThe first day of the month page on screen. Follows focus into a new month, and moves on its own when the consumer pages the grid via showMonth/showNextMonth/showPreviousMonth.
anchorDateDate | nullThe first day picked while building a range, or null when no range selection is in progress.
previewDateDate | nullThe day currently under hover/focus while a range's second endpoint is being chosen, or null when there is no pending range or no day has been previewed yet.
previewRangeCalendarModel.Range | nullThe range implied by the current anchor and preview day, normalized so start comes first; null when no range selection is in progress. Before a preview day is set it collapses to a single day on the anchor.
minDate | nullEarliest day the model accepts, or null when unbounded.
maxDate | nullLatest day the model accepts, or null when unbounded.

Methods

MethodDescription
focusDate(date: Date): voidMoves focus to an arbitrary day, clamped to the selectable bounds, and pages the visible month to match. Suits pointer selection of a grid cell; the directional focus* methods own the keyboard offset math.
focusNextDay(): voidMoves focus one day forward (typically the ArrowRight key).
focusPreviousDay(): voidMoves focus one day back (typically the ArrowLeft key).
focusNextWeek(): voidMoves focus one week forward (typically the ArrowDown key).
focusPreviousWeek(): voidMoves focus one week back (typically the ArrowUp key).
focusNextMonth(): voidMoves focus one month forward, clamping the day of month to the target month's last day when it's shorter (typically the PageDown key).
focusPreviousMonth(): voidMoves focus one month back, clamping the day of month to the target month's last day when it's shorter (typically the PageUp key).
focusMonthStart(): voidMoves focus to the first day of the focused month (typically the Home key).
focusMonthEnd(): voidMoves focus to the last day of the focused month (typically the End key).
showMonth(date: Date): voidPages the visible month to the month containing date, leaving focus where it is — the focused day scrolls out of the rendered grid until focus moves again. Suits a header's month/year picker.
showNextMonth(): voidPages the visible month forward by one, leaving focus where it is.
showPreviousMonth(): voidPages the visible month back by one, leaving focus where it is.
beginRange(date: Date): voidStarts a range selection at date, clearing any preview from a previous attempt. Call this when the user picks the first endpoint (e.g. the first click in a range calendar).
updateRangePreview(date: Date): voidUpdates the pending second endpoint so previewRange tracks the day under hover or focus. A no-op while no range is in progress, so hover handlers can call it unconditionally.
completeRange(date: Date): CalendarModel.Range | nullCommits the range from the current anchor to date and clears the pending selection, returning it normalized. With no range in progress the consumer's selected value stays untouched.
cancelRange(): voidAbandons an in-progress range selection (e.g. on Escape or blur), leaving the consumer's selected value untouched. A no-op when no range is in progress.
isFocused(date: Date): booleanWhether date is the day currently carrying keyboard focus.
isRangeAnchor(date: Date): booleanWhether date is the anchor of an in-progress range selection.
isInPreviewRange(date: Date): booleanWhether date falls within the current previewRange, inclusive of both endpoints. Always false when no range is in progress.
isInVisibleMonth(date: Date): booleanWhether date falls in the same month as visibleMonth. Useful for dimming the leading/trailing days a month grid renders from adjacent months.
isDisabled(date: Date): booleanWhether date falls outside the selectable bounds — before min, after max, or rejected by the isDateDisabled predicate.

Examples

let model = new CalendarModel({ focusedDate: new Date(2026, 0, 15) });
model.addEventListener("change", () => update());
model.focusNextDay();

The events, constants and types its module publishes, each imported from @sdxc/ui/behaviors too.

Interface

CalendarModel.Range

An inclusive start/end pair of calendar days, always normalized so start comes first, whatever order the two endpoints were picked in.

MemberTypeDescription
startDate
endDate
Interface

CalendarModel.Options

Constructor options for CalendarModel.

MemberTypeDescription
focusedDate?DateDay that starts focused. Defaults to today when omitted. Only the calendar date is read; time-of-day is discarded.
min?DateEarliest day the model will focus or accept as a range endpoint; navigation and range methods clamp to it.
max?DateLatest day the model will focus or accept as a range endpoint; navigation and range methods clamp to it.
isDateDisabled?(date: Date) => booleanPredicate marking individual days as unselectable (closures on a weekend, already-booked slots, …). Navigation still visits these days, so keyboard users can reach a selectable day past them.
Interface

CalendarModel.EventMap

Events dispatched by CalendarModel as its state changes.

MemberTypeDescription
changeEventDispatched after focus, the visible month, the range anchor, or the range preview changes.

Written by Sergio Xalambrí. Follow @sergiodxa for new packages, or sponsor the work.