GrDatePicker

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

Берут, когда дата — значение поля формы.

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

Обзор

Когда брать

  • дата — значение поля формы — контракт форм-контрола ядра работает целиком: ошибка, обязательность и подпись приезжают из GrFormField без единой правки;
  • дату можно и набрать, и выбрать — editable включает ввод с клавиатуры, разбор идёт по локали, а не по одному жёсткому шаблону;
  • часть дат недоступна — disabledDates принимает и список, и предикат: выходные, занятые слоты, «не раньше сегодня» через min/max;
  • на бэкенд уходит не Date — valueAdapter меняет тип модели на строку ISO, timestamp или свой формат, и это видно в типах.

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

НужноБерите
Период «с — по»GrDateRangePicker
Дата вместе со временемGrDateTimePicker
Только времяGrTimePicker
Сетка месяца сама по себе, без поляGrCalendar
Показать момент, а не выбрать егоGrRelativeTime
Выбирают только из готовых значений, произвольная дата не нужнаGrSelect

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

  • Дата выставления и срок оплаты счёта

    Пресеты бывают функциями: «Net 30» считается от даты выставления в момент нажатия, а не написания кода. min держит срок оплаты не раньше выставления, выходные — disabled-dates, а editable позволяет набрать дату руками. value-adapter="isoDate" делает моделью строку «2026-11-05», которую хранит API, а name отправляет её с нативной формой.

    Invoice INV-0422 · draft

    Weekends are not offered

    Payment terms: 30 days · sent as due_date=2026-11-05
  • Налоговый период: месяц или квартал

    mode меняет то, что выбирается, а не то, как хранится: панель показывает месяцы или кварталы, а модель остаётся Date — первым днём периода. max не пускает в будущие периоды: декларацию подают за прошедшее.

    VAT return
    1 Jul – 30 Sept · due 10 Nov
  • Выходные курьера набором

    multiple делает модель массивом — отдельные даты, а не диапазон. separator и format решают, как набор читается в поле, show-week-numbers сверяет с расписанием курьера, а min/max ограничивают квартал.

    Courier days off · Q4Orders placed the day before ship one day later
    4 days off in the quarter
  • Запись на звонок с панелью на странице

    inline рисует панель на месте — без поля и поповера, с той же моделью. Слот day добавляет под числом точку на каждый свободный слот, а дни без слотов — disabled-dates, их пропускает и клавиатура.

    октябрь 2026 г.
    пнвтсрчтптсбвс
    2829301234
    567891011
    12131415161718
    19202122232425
    2627282930311
    2345678
    Onboarding call · 30 minWith Grace Wu, product specialist. Dots show free slots.Pick a day with free slots.

API порождается из пакета · разойтись не может

Props

PropTypeпо умолчаниюОписание
mode"day" | "week" | "month" | "quarter" | "year" | undefined"day"Что выбирается: день, месяц или год. В режимах периода панель показывает двенадцать ячеек, а значением становится первое число периода.
modelValuev-modelTValue | readonly TValue[] | undefinedundefinedЗначение. При `multiple` — массив: набор, а не диапазон. Точнее объединения в генерик-SFC не выразить: условный тип от булева пропа Vue выводит ненадёжно, а обещание, которое иногда не работает, хуже честного `T | readonly T[]`. Рантайм-контракт при этом точный — наружу уходит ровно то, что объявлено пропом.
multipleboolean | undefinedfalseНабор дат вместо одной: расписание, даты-исключения, брони. Отличие от `GrDateRangePicker` — в существе: там непрерывный отрезок с двумя краями, здесь произвольное множество. Панель после выбора не закрывается, а клик по выбранной дате её снимает.
separatorstring | undefined", "Чем разделять даты набора в поле.
valueAdapter
"date" | "isoDate" | "isoDateTime" | "timestamp" | GrChronoA"date" | "isoDate" | "isoDateTime" | "timestamp" | GrChronoAdapter<TValue> | undefined
undefinedКак значение уходит наружу и приходит обратно: имя готового адаптера (`date`, `isoDate`, `isoDateTime`, `timestamp`) либо свой. Отдельным пропом, а не подменой типа модели: у предшественника фактический тип задавала строка, и вывести его из типов было невозможно.
minDate | undefinedundefinedСамая ранняя дата, которую можно выбрать. Дни раньше неё в панели видны, но выключены.
maxDate | undefinedundefinedСамая поздняя дата, которую можно выбрать. Дни позже неё в панели видны, но выключены.
disabledDatesreadonly Date[] | ((date: Date) => boolean) | undefinedundefinedЗапрещённые даты: список или предикат.
presetsreadonly GrDatePreset[] | undefinedundefinedГотовые даты в подвале панели: «Сегодня», «Завтра». Дату можно задать функцией — «сегодня» считается в момент показа, а не в момент объявления пропа. Функция обязана быть чистой: её зовут и чтобы решить, доступен ли шорткат, и чтобы применить его. Дата вне `min`/`max` или из `disabledDates` приходит выключенной — как и её ячейка в сетке.
weekStartIsoWeekday | undefinedundefinedПервый день недели по ISO (1 — понедельник). Не задан — из локали.
showWeekNumbersboolean | undefinedundefinedКолонка номеров недель по ISO 8601 в панели. Не задан — как у `GrCalendar` в `GrConfigProvider`, иначе `false`.
todayDate | undefinedundefinedЧто считать сегодняшним днём. Задаётся ради воспроизводимых тестов и снимков.
localestring | undefinedundefinedЛокаль показа. Не задана — из адаптера i18n приложения.
formatIntl.DateTimeFormatOptions | undefinedundefinedВид значения в поле — опциями `Intl`, а не строкой-паттерном: паттерн пришлось бы разбирать самим и он всё равно не знает порядка частей в чужой локали.

Слоты

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

События

EventСигнатураОписание
update:modelValue(e: "update:modelValue", value: TValue | readonly TValue[] | null): voidНовое значение (`v-model`); `null` — значение снято.
change(e: "change", value: TValue | readonly TValue[] | null): voidЗначение зафиксировано: выбором в панели, вводом или очисткой.
update:open(e: "update:open", value: boolean): voidПанель открылась или закрылась (`v-model:open`).
clear(e: "clear"): voidЗначение снято кнопкой очистки; только при `clearable`.
focus(e: "focus", event: FocusEvent): voidФокус вошёл в поле.
blur(e: "blur", event: FocusEvent): voidФокус ушёл из поля.

Типы

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

Импорт
import type { GrDatePickerConfigurableProps, GrDatePickerSize, GrDatePickerProps } from '@feugene/granularity-chrono/components/GrDatePicker'

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

grDatePickerConfigconst

Пикер рендерит сетку своего пакета и поповер ядра — оба ребра объявлены. Без второго потребитель, выбравший только GrDatePicker, получил бы панель без фона, рамки и тени: granum подмешивает safelist и CSS лишь тем компонентам, что попали в селекцию, а GrPopover попадает в неё только через этот граф.

const grDatePickerConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrDatePicker">;

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

GrDatePickerConfigurablePropsinterface

Пропы GrDatePicker, настраиваемые глобально через <GrConfigProvider :component-defaults="{ GrDatePicker: { … } }">. Только оформление и поведение оболочки: границы дат задаются на месте.

interface GrDatePickerConfigurableProps {
    size: GrDatePickerSize;
    clearable: boolean;
    placement: UseFloatingPlacement;
}

Файлdist/types/components/GrDatePicker/grDatePickerStyles.d.ts

GrDatePickerSizetype

Публичное имя типа размера компонента. Сами классы поля общие для всех пикеров пакета и живут в internal/pickerFieldStyles.

type GrDatePickerSize = GrPickerSize;

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

grDatePickerSafelistconst
const grDatePickerSafelist: string[];

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

GrDatePickerPropsinterface

GrDatePicker — поле с датой и панелью-календарём. Границей между мирами служит именно этот компонент: наружу он говорит на Date (или на том, что задаёт valueAdapter), внутрь — кортежами PlainDate, на которых считает весь пакет. Пока editable не включён, поле честно помечено readonly: значение меняется только через панель.

interface GrDatePickerProps<T = Date | null> {
    /**
     * Что выбирается: день, месяц или год. В режимах периода панель показывает
     * двенадцать ячеек, а значением становится первое число периода.
     */
    mode?: 'day' | 'week' | 'month' | 'quarter' | 'year';
    /**
     * Значение. При `multiple` — массив: набор, а не диапазон.
     *
     * Точнее объединения в генерик-SFC не выразить: условный тип от булева пропа
     * Vue выводит ненадёжно, а обещание, которое иногда не работает, хуже
     * честного `T | readonly T[]`. Рантайм-контракт при этом точный — наружу
     * уходит ровно то, что объявлено пропом.
     */
    modelValue?: T | readonly T[];
    /**
     * Набор дат вместо одной: расписание, даты-исключения, брони.
     *
     * Отличие от `GrDateRangePicker` — в существе: там непрерывный отрезок с
     * двумя краями, здесь произвольное множество. Панель после выбора не
     * закрывается, а клик по выбранной дате её снимает.
     */
    multiple?: boolean;
    /** Чем разделять даты набора в поле. */
    separator?: string;
    /**
     * Как значение уходит наружу и приходит обратно: имя готового адаптера
     * (`date`, `isoDate`, `isoDateTime`, `timestamp`) либо свой.
     *
     * Отдельным пропом, а не подменой типа модели: у предшественника фактический
     * тип задавала строка, и вывести его из типов было невозможно.
     */
    valueAdapter?: GrChronoAdapterName | GrChronoAdapter<T>;
    /** Самая ранняя дата, которую можно выбрать. Дни раньше неё в панели видны, но выключены. */
    min?: Date;
    /** Самая поздняя дата, которую можно выбрать. Дни позже неё в панели видны, но выключены. */
    max?: Date;
    /** Запрещённые даты: список или предикат. */
    disabledDates?: readonly Date[] | ((date: Date) => boolean);
GrDatePickerEmitsinterface
interface GrDatePickerEmits<T = Date | null> {
    /** Новое значение (`v-model`); `null` — значение снято. */
    (e: 'update:modelValue', value: T | readonly T[] | null): void;
    /** Значение зафиксировано: выбором в панели, вводом или очисткой. */
    (e: 'change', value: T | readonly T[] | null): void;
    /** Панель открылась или закрылась (`v-model:open`). */
    (e: 'update:open', value: boolean): void;
    /** Значение снято кнопкой очистки; только при `clearable`. */
    (e: 'clear'): void;
    /** Фокус вошёл в поле. */
    (e: 'focus', event: FocusEvent): void;
    /** Фокус ушёл из поля. */
    (e: 'blur', event: FocusEvent): void;
}
GrDatePresetinterface

Готовая дата в подвале панели.

interface GrDatePreset {
    label: string;
    /** Дата или функция, считающая её в момент показа и нажатия. */
    date: Date | (() => Date);

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

Готовые даты живут внутри панели

presets рисует ряд шорткатов в подвале: «Сегодня», «Завтра», «Понедельник». Дату можно задать функцией — «сегодня» считается в момент показа, а не в момент объявления пропа.

Шорткат идёт мимо сетки, поэтому проверяет запреты сам: дата вне min/max или из disabledDates приходит выключенной — ровно как её ячейка в сетке. Кнопка, которая ничего не делает, обманывает.

Свой подвал — слот footer; он заменяет ряд целиком и получает select, canSelect и close.

Значение — локальная полночь, а не момент

Дата без времени собирается как локальная полночь: пользователь выбирает день в своём календаре, а не точку на мировой линии. Кому нужен именно момент — тот сериализует его своим адаптером; молча подставлять UTC пакет не станет, потому что «12 августа» в двух часовых поясах это два разных момента и один и тот же день.

Не только дни: неделя, месяц, квартал, год

mode пробрасывается в календарь как есть, и поле показывает выбранное обычным форматом даты:

vue
<GrDatePicker v-model="reportPeriod" mode="quarter" />

Неделя кладёт в модель своё начало, квартал и месяц — первое число, год — первое января. Форма значения от режима не зависит: в поле по-прежнему одна дата, и valueAdapter работает как работал.

Разбор режимов — ./GrCalendar.md; там же о том, почему неделя рисуется сеткой дней, а не сеткой периодов, и почему номер недели не показывается.

Набор дат — не диапазон

multiple собирает произвольное множество: расписание занятий, даты-исключения в графике, забронированные дни. Модель становится массивом.

vue
<GrDatePicker v-model="lessons" multiple />

Отличие от GrDateRangePicker — в существе, а не в удобстве: там непрерывный отрезок с двумя краями и правилами длины, здесь — множество, где соседство ничего не значит.

Клик по выбранной дате её снимает. Набор — переключатель, а не накопитель: иначе снять ошибочно взятую дату было бы нечем.

Панель не закрывается после выбора. Набор набирают, а одиночную дату выбирают однажды — и закрытие после первого клика превратило бы набор из десяти дат в десять открытий панели.

Порядок всегда по возрастанию, куда бы ни кликнули. Модель обязана быть сравнима: перестановка элементов не должна читаться как изменение — иначе «есть несохранённые правки» срабатывает на пустом месте.

Ручной ввод в этом режиме выключен, даже при editable. Строка, описывающая N дат, требует своего разбора: сколько их набрано, из строки не следует, а неполный ввод не отличить от короткого набора. Диапазон и дата со временем описывают ровно два значения, и потому набираются (GrDateRangePicker, GrDateTimePicker); произвольный набор — нет.

В поле видны первые три даты и остаток числом («и ещё 2»): без потолка подпись переполняется уже на пятой. В форму уходит по скрытому полю на дату — с одним именем, как читает FormData.getAll.

min, max и disabledDates действуют на каждую дату по отдельности, теми же правилами, что и при одиночном выборе.

applyOnBlur включён

Выбор в календаре применяется сразу, а уход фокуса из поля фиксирует набранное руками. Иначе набранная, но не подтверждённая дата исчезала бы при переходе к следующему полю — а пользователь считает, что он её ввёл.

Редактируемое поле показывает то, что принимает обратно

С editable значение выводится цифрами (08/12/2026), а не Aug 12, 2026. Иначе правка числа прямо в поле оставляла бы разбору две группы цифр вместо трёх: набранное молча откатывалось бы, хотя пользователь ничего не нарушил.

Свой format это правило перебивает — тогда за читаемость набранного отвечает потребитель. Без editable показ остаётся прежним.

Запрещённая дата не принимается и текстом. disabledDates, min и max действуют одинаково на клик и на Enter; поле, обходящее ограничения панели, означало бы, что ограничений нет.