GrCalendar

  • chrono
  • Дата и время
  • API · 19 props

Берут, когда календарь и есть экран.

import { GrCalendar } from '@feugene/granularity-chrono/components/GrCalendar'

Обзор

Когда брать

  • календарь и есть экран — страница брони, планировщик, панель выбора дня: сетка нужна открытой, а не по клику в поле;
  • в ячейках своё содержимое — слот day рисует загрузку, цену, число событий поверх дня;
  • сетка нужна рядом с чем-то ещё — своя панель, свои кнопки, свой подвал через header и footer;
  • выбор дня не связан с формой — значение уходит в состояние экрана, а не в поле ввода.

Когда взять другое

НужноБерите
Дата как значение поля формыGrDatePicker
Период «с — по»GrDateRangePicker
Дата со временемGrDateTimePicker
Только времяGrTimePicker
События во времени лентой, а не сеткойGrTimeline

Календаря событий здесь нет. Сетка показывает дни, а не то, что в них происходит: недельная и дневная раскладка, перетаскивание событий, пересечения и лента ресурсов — это отдельный домен, и закрывать его будет отдельный пакет. Слот day закрывает только пометки на дне — счётчик, точку, цену.

Примеры 4 у каждого — живое демо и его код

  • Запись на приём: свободные дни и часы

    min и max держат выбор в шести неделях начиная с завтра, а disabled-dates принимает предикат — занятый день это правило, а не список. Слот day помечает дни, где ещё есть места; слот footer получает select от сетки, и «Первый свободный день» не выберет то, что сетка запрещает. change сбрасывает время при смене дня.

    Book a check-upDr. Ines Novak · Harbour Street Practice
    октябрь 2026 г.
    пнвтсрчтптсбвс
    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.

  • Два месяца, проживание и цена за ночь

    Сетка только рисует диапазон: range-start, range-end и range-preview приходят от экрана, который решает, что значит второй клик, и следит за указателем через day-hover. Два календаря делят один view-date, а слот header ставит стрелки по внешним краям. Диапазон говорит целиком, поэтому announce-selection выключен, а живой регион — строка итога; size="sm" держит два месяца рядом.

    Harbour loft · HamburgPrices per night, weekends included
    октябрь 2026 г.
    пнвтсрчтптсбвс
    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
    ноябрь 2026 г.
    пнвтсрчтптсбвс
    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.
  • Период отчёта: неделя, месяц, квартал или год

    mode превращает сетку в недели, месяцы, кварталы или годы, а модель — всегда первое число периода. В режиме недель помогают номера недель; period-change сообщает показываемый год. Пока строится отчёт, сетка disabled, а смена вида периода возвращает в неё клавиатуру через focus().

    Revenue report
    сентябрь 2026 г.
    Неделяпнвтсрчтптсбвс
    3631123456
    3778910111213
    3814151617181920
    3921222324252627
    402829301234
    41567891011
    Period28 Sept 2026 – 4 Oct 2026Only finished and current periods
  • График дежурств из отдельных дней

    selected-dates рисует набор, а экран добавляет и снимает дни по change. locale="en-US" начинает неделю с воскресенья — это решает локаль, а не экран, — а слот weekday помечает колонки выходных. Опубликованный график — readonly: читается и фокусируется, но не меняется. size="sm" держит сетку компактной рядом со сводкой; «Today» вызывает 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 порождается из пакета · разойтись не может

Props

PropTypeпо умолчаниюОписание
mode"day" | "week" | PeriodMode | undefined"day"Что выбирается: день, месяц или год. В режимах периода сетка показывает двенадцать ячеек в три колонки, а выбор отдаёт первое число периода.
modelValuev-modelPlainDate | null | undefinednullВыбранная дата. `null` — ничего не выбрано.
viewDatev-modelPlainDate | undefinedundefinedПоказываемый месяц. Без него календарь ведёт его сам, отталкиваясь от выбора.
minPlainDate | undefinedundefinedСамая ранняя дата, которую можно выбрать. Дни раньше неё видны, но выключены, а листать за её месяц нельзя.
maxPlainDate | undefinedundefinedСамая поздняя дата, которую можно выбрать. Дни позже неё видны, но выключены, а листать за её месяц нельзя.
disabledDatesreadonly PlainDate[] | ((date: PlainDate) => boolean) | undefinedundefinedЗапрещённые даты: список или предикат. Список нормализуется в `Set` один раз.
weekStartIsoWeekday | undefinedundefinedПервый день недели по ISO (1 — понедельник). Не задан — берётся из локали через `Intl`.
showWeekNumbersboolean | undefinedundefinedКолонка номеров недель по ISO 8601 слева от сетки. Не задан — из `GrConfigProvider`, иначе `false`.
todayPlainDate | undefinedundefinedЧто считать сегодняшним днём. Задаётся ради воспроизводимых тестов и снимков.
localestring | undefinedundefinedЛокаль показа. Не задана — из адаптера i18n приложения.
size"xs" | "sm" | "md" | "lg" | undefinedundefinedСтупень размера: ячейки, кегль и стрелки листания. По умолчанию `md`; берётся из `GrConfigProvider`.
disabledboolean | undefinedfalseКалендарь недоступен: выбор и листание выключены, сетка объявлена `aria-disabled`.
readonlyboolean | undefinedfalseВыбор зафиксирован: выбранная дата видна и читается диктором, но не меняется, листание тоже стоит. Сетка объявлена `aria-readonly`.
ariaLabelstring | undefinedundefinedДоступное имя сетки, когда рядом нет подписи.

Слоты

SlotTypeОписание
day{ cell: CalendarCell; selected: boolean; }Своя ячейка дня вместо числа.
header{ title: string; goToPeriod: (delta: number) => void; }Своя шапка вместо заголовка и стрелок.
weekday{ label: string; full: string; isoWeekday: IsoWeekday; }Своя ячейка шапки недели вместо сокращённого названия дня.
footer
{ select: (date: PlainDate) => boolean; canSelect: (date: Pl{ select: (date: PlainDate) => boolean; canSelect: (date: PlainDate) => boolean; }
Подвал панели: кнопки «сегодня», «очистить», готовые периоды. Выбор отдаётся внутрь, потому что снаружи его не повторить: запрет даты складывается из `min`, `max` и `disabledDates`, а `readonly` запрещает любой выбор целиком.

События

EventСигнатураОписание
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СигнатураОписание
focus() => voidПоставить фокус на ячейку, которая держит клавиатурную остановку сетки.
goToPeriod(delta: number) => voidПролистать показ на `delta` шагов: плюс — вперёд, минус — назад. Шаг — месяц, в режимах `month` и `year` — год и десятилетие; при `disabled`, `readonly` и за `min`/`max` не листает.
focusDate(date: PlainDate) => voidПеревести показ на месяц с этой датой и поставить на неё фокус.

Типы

Все экспортируемые типы подпути, по файлам, где они объявлены. Имена, дженерики и тела читаются из файлов деклараций пакета и разойтись не могут.

Импорт
import type { GrCalendarConfigurableProps, GrCalendarSize, GrCalendarProps } from '@feugene/granularity-chrono/components/GrCalendar'

Файлdist/types/components/GrCalendar/config.d.ts

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

Файлdist/types/components/GrCalendar/defaults.d.ts

GrCalendarConfigurablePropsinterface

Пропы GrCalendar, настраиваемые глобально через <GrConfigProvider :component-defaults="{ GrCalendar: { … } }">. weekStart живёт здесь, а не у каждого пикера: календарь один на всех, и первый день недели — свойство приложения, а не отдельного поля. Настройка под ключом GrCalendar доезжает и до GrDatePicker, и до диапазона, и до даты со временем, потому что панель у них общая.

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

Файлdist/types/components/GrCalendar/grCalendarStyles.d.ts

GrCalendarSizetype

Шкала размеров одна на всю систему — своего кортежа пакет не заводит.

type GrCalendarSize = GrComponentSize;

Файлdist/types/components/GrCalendar/safelist.d.ts

grCalendarSafelistconst
const grCalendarSafelist: string[];

Файлdist/types/components/GrCalendar/GrCalendar.vue.d.ts

GrCalendarPropsinterface

GrCalendar — сетка месяца по WAI-ARIA паттерну grid. Самостоятельный компонент и одновременно начинка всех пикеров пакета. Поля ввода у него нет: это именно сетка, а «поле + панель» собирается поверх неё. Значение — PlainDate, а не Date: компонент живёт внутри пакета, где Date появляется только на границе модели. Оболочка-пикер переводит одно в другое адаптером.

interface GrCalendarProps {
    /**
     * Что выбирается: день, месяц или год. В режимах периода сетка показывает
     * двенадцать ячеек в три колонки, а выбор отдаёт первое число периода.
     */
    mode?: 'day' | 'week' | PeriodMode;
    /** Выбранная дата. `null` — ничего не выбрано. */
    modelValue?: PlainDate | null;
    /** Показываемый месяц. Без него календарь ведёт его сам, отталкиваясь от выбора. */
    viewDate?: PlainDate;
    /**
     * Самая ранняя дата, которую можно выбрать. Дни раньше неё видны, но
     * выключены, а листать за её месяц нельзя.
     */
    min?: PlainDate;
    /**
     * Самая поздняя дата, которую можно выбрать. Дни позже неё видны, но
     * выключены, а листать за её месяц нельзя.
     */
    max?: PlainDate;
    /** Запрещённые даты: список или предикат. Список нормализуется в `Set` один раз. */
    disabledDates?: DisabledDatesInput;
    /**
     * Первый день недели по ISO (1 — понедельник). Не задан — берётся из локали
     * через `Intl`.
     */
    weekStart?: IsoWeekday;
    /**
     * Колонка номеров недель по ISO 8601 слева от сетки. Не задан — из
     * `GrConfigProvider`, иначе `false`.
     */
    showWeekNumbers?: boolean;
    /** Что считать сегодняшним днём. Задаётся ради воспроизводимых тестов и снимков. */
    today?: PlainDate;
    /** Локаль показа. Не задана — из адаптера i18n приложения. */
    locale?: string;
    /**
     * Ступень размера: ячейки, кегль и стрелки листания. По умолчанию `md`; берётся
     * из `GrConfigProvider`.
     */
    size?: GrCalendarSize;
    /**
     * Календарь недоступен: выбор и листание выключены, сетка объявлена
     * `aria-disabled`.
     */
    disabled?: boolean;
    /**
     * Выбор зафиксирован: выбранная дата видна и читается диктором, но не
     * меняется, листание тоже стоит. Сетка объявлена `aria-readonly`.
     */
    readonly?: boolean;
    /** Доступное имя сетки, когда рядом нет подписи. */
    ariaLabel?: string;
    /**
     * Начало выбранного диапазона. Сетка про диапазон ничего не решает — только
     * рисует его: считать границы, порядок и ограничения длины — дело пикера.
     */
    rangeStart?: PlainDate | null;
    /** Конец выбранного диапазона. */
    rangeEnd?: PlainDate | null;
    /**
     * Второй край предпросмотра, пока диапазон не закрыт: день под курсором или
     * под клавиатурной остановкой.
     */
    rangePreview?: PlainDate | null;
    /**
     * Набор выбранных дат. Как и у диапазона, сетка про него ничего не решает —
     * только рисует: складывать, снимать и сортировать набор — дело пикера.
     */
    selectedDates?: readonly PlainDate[];
    /**
     * Объявлять выбор в живом регионе. Выключается, когда объявляет оболочка:
     * у диапазона осмысленно состояние периода («начало выбрано»), а не
     * отдельный день, и два объявления на один клик перебили бы друг друга.
     */
    announceSelection?: boolean;
}
GrCalendarEmitsinterface
interface GrCalendarEmits {
    /** Выбрана дата (`v-model`). В режимах периода — первое число месяца, квартала или года. */
    (e: 'update:modelValue', value: PlainDate): void;
    /** Показываемый период сменился (`v-model:viewDate`). */
    (e: 'update:viewDate', value: PlainDate): void;
    /** Пользователь выбрал дату — кликом, клавиатурой или из подвала. */
    (e: 'change', value: PlainDate): void;
    /** Показываемый период сменился — листанием, клавиатурой или выбором. */
    (e: 'periodChange', value: PlainDate): void;
    /** День под курсором сменился. `null` — курсор ушёл из сетки. */
    (e: 'dayHover', value: PlainDate | null): void;
}

Использование из прозы библиотеки

Модель — PlainDate, а не Date

Единственный компонент пакета, у которого наружу выходит внутренний кортеж { y, m, d }, а не Date через valueAdapter. Причина в том, что календарь не форм-контрол: сериализовать ему нечего, а Date на границе означал бы полночь в какой-то зоне — ровно ту неоднозначность, ради ухода от которой пакет и считает на кортежах (../model.md).

Значение нужно в виде Date — его собирают на своей стороне; пикеры делают то же самое, только внутри себя.

Первый день недели — из локали, а не из пропа

Понедельник в России и воскресенье в США — это Intl, а не настройка компонента. Проп locale меняет и названия, и порядок дней разом; порознь их менять нельзя, иначе получится календарь, которого не существует ни в одной стране.

Когда локаль всё же нужно перебить — например, рабочая неделя начинается с воскресенья независимо от языка интерфейса, — это решение приложения, а не экрана. Такое ставится один раз через GrConfigProvider:

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

Настройка идёт под ключом GrCalendar и доезжает до всех пикеров: панель у них общая, своего ключа у GrDatePicker и соседей нет намеренно. Порядок разрешения обычный — проп на месте сильнее конфига, конфиг сильнее локали; не задано ничего — решает Intl.

Сетка рисует выбор, но не считает его

Диапазон приходит в сетку двумя пропами (rangeStart, rangeEnd), набор — одним (selectedDates), и ни один из них сетка не изменяет: складывать, снимать, сортировать и проверять длину — дело пикера. Календарь отвечает на один вопрос: как покрасить ячейку.

Из-за этого он одинаково служит и GrDatePicker с его набором, и GrDateRangePicker с отрезком, и приложению, которое ведёт выбор само.

Вхождение в набор считается по Set, собранному один раз на смену набора: обход массива на каждую из сорока двух ячеек превратил бы подсветку в квадрат.

Пять режимов, и неделя среди них — не сетка периодов
modeЧто показываетЧто кладёт в модель
day (по умолчанию)сетку днейвыбранный день
weekту же сетку днейначало выбранной недели
monthдвенадцать месяцевпервое число месяца
quarterчетыре кварталапервое число квартала
yearдвенадцать лет десятилетияпервое января

Неделя рисуется сеткой дней, а не сеткой периодов. Двенадцать ячеек в три колонки — это двенадцать недель на экране, четверть года без единой подписи месяца: выбирать в такой сетке нечего. Вместо этого клик по любому дню выбирает неделю, в которую он попал, и строка подсвечивается целиком.

Начало недели берётся из локали (см. «Первый день недели» выше), а не прибивается к понедельнику: в США та же дата попадает в неделю, начинающуюся воскресеньем. В модель кладётся одна дата — начало недели, — и форма значения остаётся общей для всех режимов: valueAdapter работает как работал.

Номера недели пакет не показывает: у ISO и у США он считается по-разному, а Intl его не даёт — выдумывать локале-зависимое мимо Intl пакет не берётся.

Кварталов четыре, и сетка у них в две колонки — три оставили бы одинокую ячейку во второй строке. Подпись берётся строкой локали пакета (Q1, 1 кв.): Intl кварталы не именует вовсе, и это интерфейсный текст, а не локале-зависимые данные.

Детерминированный рендер требует today

Часы среды читаются один раз на экземпляр. Там, где рендер обязан совпасть с прошлым — снимки, тесты, серверная отрисовка, — «сегодня» задаётся пропом today: иначе первый клиентский рендер разойдётся с серверным ровно в полночь. Подробности — ../ssr.md.