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 89 7 89 8 89 9 119 10 119 11 89 12 89 13 89 14 89 15 89 16 119 17 119 18 89 19 89 20 89 21 89 22 89 23 24 25 89 26 89 27 89 28 89 29 89 30 119 31 119 1 2 3 4 5 6 7 8 ноябрь 2026 г.пн вт ср чт пт сб вс 26 27 28 29 30 31 1 89 2 89 3 89 4 89 5 89 6 119 7 119 8 89 9 89 10 89 11 89 12 89 13 119 14 119 15 89 16 89 17 89 18 89 19 89 20 119 21 119 22 89 23 89 24 89 25 89 26 89 27 119 28 119 29 89 30 89 1 2 3 4 5 6 Check-in 16 Oct — now the check-out day.Период отчёта: неделя, месяц, квартал или год
modeпревращает сетку в недели, месяцы, кварталы или годы, а модель — всегда первое число периода. В режиме недель помогают номера недель;period-changeсообщает показываемый год. Пока строится отчёт, сеткаdisabled, а смена вида периода возвращает в неё клавиатуру черезfocus().Revenue reportсентябрь 2026 г.Неделя пн вт ср чт пт сб вс 36 31 1 2 3 4 5 6 37 7 8 9 10 11 12 13 38 14 15 16 17 18 19 20 39 21 22 23 24 25 26 27 40 28 29 30 1 2 3 4 41 5 6 7 8 9 10 11 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 YorkOctober 2026Sun Mon Tue Wed Thu Fri Sat 27 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 6 days on call · 4 at weekendsNextThu, Oct 8Wed, Oct 14Sat, Oct 17
API порождается из пакета · разойтись не может
Props
| Prop | Type | по умолчанию | Описание |
|---|---|---|---|
mode | "day" | "week" | PeriodMode | undefined | "day" | Что выбирается: день, месяц или год. В режимах периода сетка показывает двенадцать ячеек в три колонки, а выбор отдаёт первое число периода. |
modelValuev-model | PlainDate | null | undefined | null | Выбранная дата. `null` — ничего не выбрано. |
viewDatev-model | PlainDate | undefined | undefined | Показываемый месяц. Без него календарь ведёт его сам, отталкиваясь от выбора. |
min | PlainDate | undefined | undefined | Самая ранняя дата, которую можно выбрать. Дни раньше неё видны, но выключены, а листать за её месяц нельзя. |
max | PlainDate | undefined | undefined | Самая поздняя дата, которую можно выбрать. Дни позже неё видны, но выключены, а листать за её месяц нельзя. |
disabledDates | readonly PlainDate[] | ((date: PlainDate) => boolean) | undefined | undefined | Запрещённые даты: список или предикат. Список нормализуется в `Set` один раз. |
weekStart | IsoWeekday | undefined | undefined | Первый день недели по ISO (1 — понедельник). Не задан — берётся из локали через `Intl`. |
showWeekNumbers | boolean | undefined | undefined | Колонка номеров недель по ISO 8601 слева от сетки. Не задан — из `GrConfigProvider`, иначе `false`. |
today | PlainDate | undefined | undefined | Что считать сегодняшним днём. Задаётся ради воспроизводимых тестов и снимков. |
locale | string | undefined | undefined | Локаль показа. Не задана — из адаптера i18n приложения. |
size | "xs" | "sm" | "md" | "lg" | undefined | undefined | Ступень размера: ячейки, кегль и стрелки листания. По умолчанию `md`; берётся из `GrConfigProvider`. |
disabled | boolean | undefined | false | Календарь недоступен: выбор и листание выключены, сетка объявлена `aria-disabled`. |
readonly | boolean | undefined | false | Выбор зафиксирован: выбранная дата видна и читается диктором, но не меняется, листание тоже стоит. Сетка объявлена `aria-readonly`. |
ariaLabel | string | undefined | undefined | Доступное имя сетки, когда рядом нет подписи. |
rangeStart | PlainDate | null | undefined | null | Начало выбранного диапазона. Сетка про диапазон ничего не решает — только рисует его: считать границы, порядок и ограничения длины — дело пикера. |
rangeEnd | PlainDate | null | undefined | null | Конец выбранного диапазона. |
rangePreview | PlainDate | null | undefined | null | Второй край предпросмотра, пока диапазон не закрыт: день под курсором или под клавиатурной остановкой. |
announceSelection | boolean | undefined | true | Объявлять выбор в живом регионе. Выключается, когда объявляет оболочка: у диапазона осмысленно состояние периода («начало выбрано»), а не отдельный день, и два объявления на один клик перебили бы друг друга. |
selectedDates | readonly PlainDate[] | undefined | — | Набор выбранных дат. Как и у диапазона, сетка про него ничего не решает — только рисует: складывать, снимать и сортировать набор — дело пикера. |
Слоты
| Slot | Type | Описание |
|---|---|---|
day | { cell: CalendarCell; selected: boolean; } | Своя ячейка дня вместо числа. |
header | { title: string; goToPeriod: (delta: number) => void; } | Своя шапка вместо заголовка и стрелок. |
weekday | { label: string; full: string; isoWeekday: IsoWeekday; } | Своя ячейка шапки недели вместо сокращённого названия дня. |
footer |
| Подвал панели: кнопки «сегодня», «очистить», готовые периоды. Выбор отдаётся внутрь, потому что снаружи его не повторить: запрет даты складывается из `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
const grCalendarConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrCalendar">;Файлdist/types/components/GrCalendar/defaults.d.ts
Пропы GrCalendar, настраиваемые глобально через
<GrConfigProvider :component-defaults="{ GrCalendar: { … } }">.
weekStart живёт здесь, а не у каждого пикера: календарь один на всех, и
первый день недели — свойство приложения, а не отдельного поля. Настройка
под ключом GrCalendar доезжает и до GrDatePicker, и до диапазона, и до
даты со временем, потому что панель у них общая.
interface GrCalendarConfigurableProps {
size: GrCalendarSize;
showWeekNumbers: boolean;
weekStart: IsoWeekday;
}Файлdist/types/components/GrCalendar/grCalendarStyles.d.ts
Шкала размеров одна на всю систему — своего кортежа пакет не заводит.
type GrCalendarSize = GrComponentSize;Файлdist/types/components/GrCalendar/safelist.d.ts
const grCalendarSafelist: string[];Файлdist/types/components/GrCalendar/GrCalendar.vue.d.ts
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;
}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:
<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.
Пропы этого примера