GrCalendar

  • chrono
  • chrono
  • API · 19 props
import { GrCalendar } from '@feugene/granularity-chrono/components/GrCalendar'

Overview

When to take it

  • the calendar is the screen — a booking page, a planner, a day-selection panel: the grid has to be open rather than opened by a click in a field;
  • the cells have content of their own — the day slot draws the load, the price, the number of events over a day;
  • the grid is needed next to something else — a panel of your own, buttons of your own, a footer of your own through header and footer;
  • the choice of a day is not related to a form — the value goes into the state of the screen rather than into an input field.

When to take something else

NeedTake
A date as the value of a form fieldGrDatePicker
A “from — to” periodGrDateRangePicker
A date with a timeGrDateTimePicker
A time onlyGrTimePicker
Events in time as a feed rather than as a gridGrTimeline

There is no event calendar here. The grid shows the days rather than what happens in them: a week and a day layout, dragging events, overlaps and a feed of resources are a separate domain, and a separate package will cover it. The day slot covers only the marks on a day — a counter, a dot, a price.

Examples 4 each with a live demo and its code

  • An appointment: free days and free times

    min and max keep the choice within six weeks from tomorrow, and disabled-dates takes a predicate — a full day is a rule, not a list. The day slot marks the days that still have room; the footer slot gets select from the grid, so «First free day» cannot pick what the grid forbids. change resets the time when the day changes.

    Book a check-upDr. Ines Novak · Harbour Street Practice
    October 2026
    MonTueWedThuFriSatSun
    28 29 30 1 2 3 4
    5 6 7 8 9 10 11
    12 13 14 15 16 17 18
    19 20 21 22 23 24 25
    26 27 28 29 30 31 1
    2 3 4 5 6 7 8
    Pick a dayDays with a green dot still have free times.

  • Two months, a stay and a price per night

    The grid only draws the range: range-start, range-end and range-preview come from the screen, which decides what the second click means and follows the pointer through day-hover. Two calendars share one view-date, and the header slot puts the arrows on the outer edges. The range speaks as a whole, so announce-selection is off and the summary line is the live region; size="sm" keeps two months side by side.

    Harbour loft · HamburgPrices per night, weekends included
    October 2026
    MonTueWedThuFriSatSun
    28 29 30 1 2 3 4
    5 6 897 898 899 11910 11911 89
    12 8913 8914 8915 8916 11917 11918 89
    19 8920 8921 8922 8923 24 25 89
    26 8927 8928 8929 8930 11931 1191
    2 3 4 5 6 7 8
    November 2026
    MonTueWedThuFriSatSun
    26 27 28 29 30 31 1 89
    2 893 894 895 896 1197 1198 89
    9 8910 8911 8912 8913 11914 11915 89
    16 8917 8918 8919 8920 11921 11922 89
    23 8924 8925 8926 8927 11928 11929 89
    30 891 2 3 4 5 6
    Check-in 16 Oct — now the check-out day.
  • A report period: week, month, quarter or year

    mode turns the grid into weeks, months, quarters or years, and the model is always the first day of the period. Week numbers help in week mode; period-change reports the year being shown. While the report builds, the grid is disabled, and a new kind of period returns the keyboard to it through focus().

    Revenue report
    September 2026
    WeekMonTueWedThuFriSatSun
    3631123456
    3778910111213
    3814151617181920
    3921222324252627
    402829301234
    41567891011
    Period28 Sept 2026 – 4 Oct 2026Only finished and current periods
  • An on-call rota of separate days

    selected-dates draws a set, and the screen adds and removes days on change. locale="en-US" starts the week on Sunday — the locale decides that, not the screen — and the weekday slot marks the weekend columns. A published rota is readonly: readable and focusable, but fixed. size="sm" keeps the grid compact next to the summary; «Today» calls focusDate().

    On-call · Grace WuSupport team, New York
    October 2026
    SunMonTueWedThuFriSat
    27282930123
    45678910
    11121314151617
    18192021222324
    25262728293031
    1234567
    6 days on call · 4 at weekendsNextThu, Oct 8Wed, Oct 14Sat, Oct 17

API generated from the package · cannot drift

Props

PropTypedefaultDescription
mode"day" | "week" | PeriodMode | undefined"day"What gets selected: a day, a month or a year. In period modes the grid shows twelve cells in three columns, and the selection returns the first day of the period.
modelValuev-modelPlainDate | null | undefinednullSelected date. `null` — nothing is selected.
viewDatev-modelPlainDate | undefinedundefinedThe month being shown. Without it the calendar keeps track of it itself, starting from the selection.
minPlainDate | undefinedundefinedThe earliest date that can be selected. Days before it are visible but disabled, and paging past its month is not possible.
maxPlainDate | undefinedundefinedThe latest date that can be selected. Days after it are visible but disabled, and paging past its month is not possible.
disabledDatesreadonly PlainDate[] | ((date: PlainDate) => boolean) | undefinedundefinedDisabled dates: a list or a predicate. A list is normalized into a `Set` once.
weekStartIsoWeekday | undefinedundefinedFirst day of the week per ISO (1 — Monday). Not set — taken from the locale via `Intl`.
showWeekNumbersboolean | undefinedundefinedA column of ISO 8601 week numbers to the left of the grid. Not set — from `GrConfigProvider`, otherwise `false`.
todayPlainDate | undefinedundefinedWhat counts as today. Set for reproducible tests and snapshots.
localestring | undefinedundefinedDisplay locale. Not set — from the application i18n adapter.
size"xs" | "sm" | "md" | "lg" | undefinedundefinedThe size step: the cells, the type size and the paging arrows. `md` by default; taken from `GrConfigProvider`.
disabledboolean | undefinedfalseThe calendar is unavailable: selection and paging are switched off, the grid is marked `aria-disabled`.
readonlyboolean | undefinedfalseThe selection is fixed: the selected date is visible and read out by a screen reader but does not change, and paging is stopped too. The grid is marked `aria-readonly`.
ariaLabelstring | undefinedundefinedAccessible name for the grid when there is no label nearby.

Slots

SlotTypeDescription
day{ cell: CalendarCell; selected: boolean; }A custom day cell instead of the number.
header{ title: string; goToPeriod: (delta: number) => void; }A custom header instead of the title and the arrows.
weekday{ label: string; full: string; isoWeekday: IsoWeekday; }A custom weekday header cell instead of the abbreviated day name.
footer
{ select: (date: PlainDate) => boolean; canSelect: (date: Pl{ select: (date: PlainDate) => boolean; canSelect: (date: PlainDate) => boolean; }
Panel footer: "today" and "clear" buttons, preset periods. The selection is passed inside because it cannot be reproduced from outside: a disabled date adds up from `min`, `max` and `disabledDates`, and `readonly` forbids any selection at all.

Events

EventSignatureDescription
update:modelValue(event: "update:modelValue", value: PlainDate): void—
update:viewDate(event: "update:viewDate", value: PlainDate): void—
change(event: "change", value: PlainDate): void—
periodChange(event: "periodChange", value: PlainDate): void—
dayHover(event: "dayHover", value: PlainDate | null): void—

Methods

MethodsSignatureDescription
focus() => voidPut focus on the cell that holds the keyboard stop of the grid.
goToPeriod(delta: number) => voidPage the view by `delta` steps: plus — forward, minus — back. A step is a month, in the `month` and `year` modes — a year and a decade; it does not page with `disabled`, `readonly` or beyond `min`/`max`.
focusDate(date: PlainDate) => voidMove the view to the month with this date and put focus on it.

Types

Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.

Import
import type { GrCalendarConfigurableProps, GrCalendarSize, GrCalendarProps } from '@feugene/granularity-chrono/components/GrCalendar'

Filedist/types/components/GrCalendar/config.d.ts

grCalendarConfigconst
const grCalendarConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrCalendar">;

Filedist/types/components/GrCalendar/defaults.d.ts

GrCalendarConfigurablePropsinterface
interface GrCalendarConfigurableProps {
    size: GrCalendarSize;
    showWeekNumbers: boolean;
    weekStart: IsoWeekday;
}

Filedist/types/components/GrCalendar/grCalendarStyles.d.ts

GrCalendarSizetype
type GrCalendarSize = GrComponentSize;

Filedist/types/components/GrCalendar/safelist.d.ts

grCalendarSafelistconst
const grCalendarSafelist: string[];

Filedist/types/components/GrCalendar/GrCalendar.vue.d.ts

GrCalendarPropsinterface
interface GrCalendarProps {
    mode?: 'day' | 'week' | PeriodMode;
    modelValue?: PlainDate | null;
    viewDate?: PlainDate;
    min?: PlainDate;
    max?: PlainDate;
    disabledDates?: DisabledDatesInput;
    weekStart?: IsoWeekday;
    showWeekNumbers?: boolean;
    today?: PlainDate;
    locale?: string;
    size?: GrCalendarSize;
    disabled?: boolean;
    readonly?: boolean;
    ariaLabel?: string;
    rangeStart?: PlainDate | null;
    rangeEnd?: PlainDate | null;
    rangePreview?: PlainDate | null;
    selectedDates?: readonly PlainDate[];
    announceSelection?: boolean;
}
GrCalendarEmitsinterface
interface GrCalendarEmits {
    (e: 'update:modelValue', value: PlainDate): void;
    (e: 'update:viewDate', value: PlainDate): void;
    (e: 'change', value: PlainDate): void;
    (e: 'periodChange', value: PlainDate): void;
    (e: 'dayHover', value: PlainDate | null): void;
}

Usage from the library prose

The model is a PlainDate rather than a Date

The only component of the package whose internal { y, m, d } tuple goes outside rather than a Date through a valueAdapter. The reason is that a calendar is not a form control: it has nothing to serialise, and a Date at the boundary would mean midnight in some zone — exactly the ambiguity for the sake of avoiding which the package computes on tuples (../model.md).

If the value is needed as a Date, it is assembled on your own side; the pickers do the same, only inside themselves.

The first day of the week comes from the locale rather than from a prop

Monday in Russia and Sunday in the USA is Intl rather than a setting of the component. The locale prop changes both the names and the order of the days at once; changing them separately is not allowed, otherwise a calendar comes out that does not exist in any country.

When the locale does have to be overridden — for instance the working week starts on Sunday regardless of the language of the interface — that is a decision of the application rather than of a screen. Such a thing is set once through GrConfigProvider:

vue
<GrConfigProvider :component-defaults="{ GrCalendar: { weekStart: 7, showWeekNumbers: true } }">

The setting goes under the GrCalendar key and reaches all of the pickers: their panel is shared, and GrDatePicker and its neighbours deliberately have no key of their own. The resolution order is the usual one — the prop in place is stronger than the config, and the config is stronger than the locale; if nothing is set, Intl decides.

The grid draws the selection but does not compute it

A range arrives in the grid as two props (rangeStart, rangeEnd) and a set as one (selectedDates), and the grid changes neither of them: adding, removing, sorting and checking the length are the business of the picker. The calendar answers one question: how to colour a cell.

Because of that it serves equally well GrDatePicker with its set, GrDateRangePicker with its segment, and an application that runs the selection itself.

Membership of the set is computed with a Set assembled once per change of the set: walking the array for each of the forty-two cells would turn the highlighting into a quadratic.

Five modes, and the week among them is not a grid of periods
modeWhat it showsWhat it puts into the model
day (the default)a grid of daysthe selected day
weekthe same grid of daysthe beginning of the selected week
monthtwelve monthsthe first day of the month
quarterfour quartersthe first day of the quarter
yearthe twelve years of a decadethe first of January

A week is drawn as a grid of days rather than as a grid of periods. Twelve cells in three columns are twelve weeks on the screen, a quarter of a year without a single month label: there is nothing to choose in such a grid. Instead a click on any day selects the week it fell into, and the row is highlighted as a whole.

The beginning of the week is taken from the locale (see “The first day of the week” above) rather than nailed to Monday: in the USA the same date falls into a week starting on Sunday. One date is put into the model — the beginning of the week — and the shape of the value stays shared across all of the modes: valueAdapter works as it worked.

The package does not show week numbers: ISO and the USA count them differently, and Intl does not provide it — the package does not undertake to invent something locale-dependent past Intl.

There are four quarters, and their grid has two columns — three would leave a lonely cell in the second row. The label is taken from a string of the package locale (Q1, 1 qtr): Intl does not name quarters at all, and this is interface text rather than locale-dependent data.

A deterministic render requires today

The clock of the environment is read once per instance. Where the render is obliged to match a past one — snapshots, tests, server rendering — “today” is set with the today prop: otherwise the first client render will diverge from the server one exactly at midnight. The details — ../ssr.md.