GrDateRangePicker
Берут, когда отчёт за период.
Обзор
Когда брать
- отчёт за период — «с — по» это одно поле и одно значение, а не два
независимых: два
GrDatePickerрядом не мешают выбрать конец раньше начала; - бронирование и занятость — предпросмотр по наведению показывает будущий диапазон до второго клика;
- фильтр списка по датам — модель это пара, поэтому в query-string она уезжает одним параметром;
- границы обязаны быть согласованы — компонент сам держит порядок и не выпускает наружу перевёрнутый период.
Когда взять другое
| Нужно | Берите |
|---|---|
| Одна дата | GrDatePicker |
| Дата со временем | GrDateTimePicker |
| Только время | GrTimePicker |
| Сетка месяца без поля | GrCalendar |
| Период выбирают только из готовых, свой не нужен | GrSegmented |
Примеры 3 у каждого — живое демо и его код
Период отчёта
Обе границы или ничего: полувыбранный период значением не считается. Пресеты — функции, поэтому «Last 30 days» считается в момент нажатия;
maxне пускает в будущее.value-adapter="isoDate"делает каждую границу строкой «2026-09-07» — параметрами запроса отчёта, — аnameотправляет их двумя скрытыми полями.Revenue reportGET /v1/reports/revenue?from=2026-09-07&to=2026-10-06Заявка на отпуск по правилам компании
min-rangeиmax-range— свойства пары, а не даты: ни одна граница не скажет «не больше двух недель». Выходные и праздники —disabled-dates, поэтому заявка на них не начинается и не заканчивается, а страница считает рабочие дни между границами.Окно обслуживания с точностью до четверти часа
enable-timeдобавляет часы и минуты обеим границам, аminute-step="15"держит объявленное окно круглым: поле, допускающее 02:07, приглашает к обещанию, которое никто не сдержит. 24-часовой формат берётся из локали, еслиuse-12-hoursне говорит иного.Planned maintenance · invoices APIShown on the status page and in the customer emails 2 h 30 min of downtime
API порождается из пакета · разойтись не может
Props
| Prop | Type | по умолчанию | Описание |
|---|---|---|---|
modelValuev-model | readonly [TItem, TItem] | null | undefined | undefined | Обе границы или ничего: полупустой диапазон значением не считается. |
valueAdapter |
| undefined | Как границы уходят наружу и приходят обратно. Адаптер применяется к каждой отдельно, поэтому модель — пара `[T, T]`. |
min | Date | undefined | undefined | Самая ранняя дата, которую можно выбрать. Дни раньше неё в панели видны, но выключены. |
max | Date | undefined | undefined | Самая поздняя дата, которую можно выбрать. Дни позже неё в панели видны, но выключены. |
disabledDates | readonly Date[] | ((date: Date) => boolean) | undefined | undefined | Запрещённые даты: список или предикат. |
enableTime | boolean | undefined | false | Границы периода задаются с точностью до минут, а не полуночью. Панель получает две колонки времени — для начала и для конца. Порядок произвольный: даты выбираются кликами, время правится когда угодно. Мастера из четырёх шагов здесь нет намеренно — он завёл бы скрытое состояние «на каком мы шаге», из которого промахнувшегося не вывести иначе как пройдя всё заново. |
minuteStep | number | undefined | 1 | Шаг колонки минут в минутах. По умолчанию `1`; осмыслен с `enableTime`. |
secondStep | number | undefined | 1 | Шаг колонки секунд в секундах. По умолчанию `1`; осмыслен с `enableSeconds`. |
enableSeconds | boolean | undefined | false | Колонка секунд в панели и секунды в поле. По умолчанию выключена. |
use12Hours | boolean | undefined | undefined | Не задан — берётся из локали. |
minRange | number | undefined | undefined | Наименьшая длина периода в днях, считая обе границы. |
maxRange | number | undefined | undefined | Наибольшая длина периода в днях, считая обе границы. |
presets | readonly GrDateRangePreset[] | undefined | undefined | Готовые периоды в подвале панели: «Сегодня», «Последние 7 дней». Границы можно задать функцией — «последние 7 дней» отсчитываются от сегодняшнего дня, а не от дня, когда объявили проп. Функция обязана быть чистой: её зовут и чтобы решить, доступен ли шорткат, и чтобы применить его. Период вне `min`/`max`, задевающий `disabledDates` или нарушающий `minRange`/`maxRange`, приходит выключенным: кнопка, которая ничего не делает, обманывает. |
weekStart | IsoWeekday | undefined | undefined | Первый день недели по ISO (1 — понедельник). Не задан — из локали. |
showWeekNumbers | boolean | undefined | undefined | Колонка номеров недель по ISO 8601 в панели. Не задан — как у `GrCalendar` в `GrConfigProvider`, иначе `false`. |
today | Date | undefined | undefined | Что считать сегодняшним днём. Задаётся ради воспроизводимых тестов. |
format | Intl.DateTimeFormatOptions | undefined | undefined | Вид границ в поле — опциями `Intl`, а не строкой-паттерном. |
separator | string | undefined | " — " | Разделитель границ в поле. |
placeholder | string | undefined | undefined | Подсказка в пустом поле. Не задана, а поле редактируемое (`editable`) — показывается порядок частей даты из локали. |
editable | boolean | undefined | false | Период можно набрать руками одной строкой. Порядок частей и разделители берутся из локали; чем разделены сами границы, разбору безразлично. |
applyOnBlur | boolean | undefined | true | Набранное фиксируется на уходе фокуса, а не только по `Enter`. |
clearable | boolean | undefined | undefined | Кнопка очистки в поле: снимает значение и шлёт `clear`. Не задан — из `GrConfigProvider`, иначе `false`. |
openv-model | boolean | undefined | undefined | Контролируемое состояние панели (`v-model:open`). |
inline | boolean | undefined | false | Панель рисуется на месте: ни поля, ни поповера. Модель, адаптер и `name` остаются пикеровскими — этим `inline` и отличается от голого `GrCalendar`, который говорит кортежами. |
placement | Placement | undefined | undefined | Сторона поля, у которой раскрывается панель. Не задан — из `GrConfigProvider`, иначе `bottom-start`. |
teleportTo | string | HTMLElement | undefined | undefined | Точечное переопределение точки монтирования панели. |
size | "xs" | "sm" | "md" | "lg" | undefined | undefined | Высота и кегль поля по шкале контролов. По умолчанию `md`; берётся из `GrConfigProvider`. |
locale | string | undefined | undefined | Локаль показа. Не задана — из адаптера i18n приложения. |
id | string | undefined | undefined | Собственный `id` поля. Не задан — берётся из `GrFormField`. |
name | string | undefined | undefined | Имя для нативной формы: границы уходят двумя скрытыми полями. |
disabled | boolean | undefined | false | Поле недоступно: панель не открывается, значение не меняется. Наследуется от `GrFormField`. |
readonly | boolean | undefined | false | Значение видно, панель открывается, но выбор не меняется. |
invalid | boolean | undefined | false | Визуальное и ARIA-состояние ошибки (`aria-invalid`). Наследуется от `GrFormField`. |
required | boolean | undefined | false | Обязательное поле (`aria-required`). Наследуется от `GrFormField`. |
loading | boolean | undefined | false | Фоновая работа: спиннер в поле и `aria-busy`. |
ariaLabel | string | undefined | undefined | Доступное имя поля, когда видимой подписи нет. Внутри `GrFormField` имя даёт его `label`. |
Слоты
| Slot | Type | Описание |
|---|---|---|
day | { cell: CalendarCell; selected: boolean; } | Своя ячейка дня вместо числа. |
header | { title: string; goToPeriod: (delta: number) => void; } | Своя шапка сетки вместо заголовка и стрелок. |
weekday | { label: string; full: string; isoWeekday: IsoWeekday; } | Своя ячейка шапки недели вместо сокращённого названия дня. |
footer |
| Подвал панели. Выбор периода отдаётся внутрь: шорткат обязан выставить **обе** границы и не обойти `minRange`/`maxRange`, `min`/`max` и `disabledDates`, а снаружи этих правил не видно. |
События
| Event | Сигнатура | Описание |
|---|---|---|
update:modelValue | (e: "update:modelValue", value: readonly [TItem, TItem] | null): void | Новое значение (`v-model`); `null` — значение снято. |
change | (e: "change", value: readonly [TItem, TItem] | 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 { GrDateRangePickerConfigurableProps, GrDateRangePickerSize, GrDateRangePickerProps } from '@feugene/granularity-chrono/components/GrDateRangePicker'Файлdist/types/components/GrDateRangePicker/config.d.ts
Панель собрана из чужой разметки: сетка — GrCalendar, колонки времени —
внутренний компонент GrTimePicker, поповер и кнопки — ядро. Все рёбра
объявлены, иначе потребитель, выбравший один GrDateRangePicker, получил бы
панель без стилей.
Ребро к GrTimePicker безусловно, хотя колонки включает необязательный
enable-time: селекция статична, и по ней не видно, каким пропом компонент
будет вызван, — а импорт колонок в собранном чанке есть всегда.
const grDateRangePickerConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrDateRangePicker">;Файлdist/types/components/GrDateRangePicker/defaults.d.ts
Пропы GrDateRangePicker, настраиваемые глобально через
<GrConfigProvider :component-defaults="{ GrDateRangePicker: { … } }">.
interface GrDateRangePickerConfigurableProps {
size: GrDateRangePickerSize;
clearable: boolean;
placement: UseFloatingPlacement;
}Файлdist/types/components/GrDateRangePicker/grDateRangePickerStyles.d.ts
Публичное имя типа размера компонента. Классы поля общие для всех пикеров
пакета, полосу диапазона рисует GrCalendar — своих классов у пикера нет.
type GrDateRangePickerSize = GrPickerSize;Файлdist/types/components/GrDateRangePicker/safelist.d.ts
const grDateRangePickerSafelist: string[];Файлdist/types/components/GrDateRangePicker/GrDateRangePicker.vue.d.ts
GrDateRangePicker — выбор периода одной сеткой. Диапазон набирается двумя кликами: первый ставит начало, второй закрывает период и отдаёт его наружу. Пока период открыт, сетка показывает предпросмотр до дня под курсором — и считается он **на отрисовке**, сравнением кортежей: ячейки про выбор ничего не знают, иначе движение мыши пересобирало бы всю сетку на каждый кадр. Границы можно вести и назад — порядок нормализуется при закрытии периода.
interface GrDateRangePickerProps<T = Date> {
/** Обе границы или ничего: полупустой диапазон значением не считается. */
modelValue?: readonly [T, T] | null;
/**
* Как границы уходят наружу и приходят обратно. Адаптер применяется к каждой
* отдельно, поэтому модель — пара `[T, T]`.
*/
valueAdapter?: GrChronoAdapterName | GrChronoAdapter<T>;
/** Самая ранняя дата, которую можно выбрать. Дни раньше неё в панели видны, но выключены. */
min?: Date;
/** Самая поздняя дата, которую можно выбрать. Дни позже неё в панели видны, но выключены. */
max?: Date;
/** Запрещённые даты: список или предикат. */
disabledDates?: readonly Date[] | ((date: Date) => boolean);interface GrDateRangePickerEmits<T = Date> {
/** Новое значение (`v-model`); `null` — значение снято. */
(e: 'update:modelValue', value: readonly [T, T] | null): void;
/** Значение зафиксировано: выбором в панели, вводом или очисткой. */
(e: 'change', value: readonly [T, 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;
}Готовый период в подвале панели.
interface GrDateRangePreset {
label: string;
/** Пара границ или функция, считающая их в момент показа и нажатия. */
range: readonly [Date, Date] | (() => readonly [Date, Date]);Использование из прозы библиотеки
Готовые периоды живут внутри панели
presets рисует ряд шорткатов в подвале: «Сегодня», «Последние 7 дней», «Этот
месяц». Границы можно задать функцией — «последние 7 дней» отсчитываются от
сегодняшнего дня, а не от дня, когда объявили проп.
Соседний GrSegmented для этого не годится, хотя внешне похож. Шорткат периода
обязан выставить обе границы и не обойти minRange/maxRange, min, max
и disabledDates — то есть знать внутренности пикера. Снаружи этих правил не
видно, поэтому период, который выбрать нельзя, снаружи и не отличить от
допустимого; внутри он приходит выключенным.
Свой подвал — слот footer; он заменяет ряд целиком и получает setRange,
canSetRange и close, то есть ровно те же правила.
Период с точностью до минут
enable-time добавляет в панель две колонки времени — для начала и для конца:
<GrDateRangePicker v-model="window" enable-time />Свежий период получает сутки целиком: 00:00 и 23:59. Две полуночи выглядели бы симметрично, но
«с 1 по 3 августа» по-человечески включает весь третий день, а [1 авг 00:00, 3 авг 00:00) молча
отрезал бы почти всё третье — классическая ошибка отчётов. С enable-seconds конец получает
23:59:59. Оба значения видны и правятся.
Конец кладётся на ту же сетку, что и колонки: при minute-step="15" это 23:45, а не 23:59.
Иначе у минут не было бы выбранного варианта — конец периода нельзя было бы прочитать там, где его
правят. С шагом в минуту, то есть по умолчанию, это ровно 23:59.
Мастера из четырёх шагов здесь нет. Даты выбираются двумя кликами, как и без времени; время правится когда угодно и в любом порядке. Пошаговый выбор завёл бы скрытое состояние «на каком мы шаге», из которого промахнувшегося не вывести иначе как пройдя всё заново.
Панель не закрывается на второй дате, когда время включено: выбор на ней не заканчивается, и
закрытие отняло бы у пользователя колонки, ради которых он enable-time и включил.
Внутри одного дня порядок краёв держит только время. Правка, после которой конец оказался бы раньше начала, не применяется и объявляется — тем же приёмом, каким отклоняется недопустимая длина: пользователь просто промахнулся, и сбрасывать ему выбор не за что.
minRange и maxRange по-прежнему считаются в днях. Менять единицу у существующих пропов
значило бы сломать чужой код; ограничение в минутах — это отдельные minDuration/maxDuration, и
они появятся, когда понадобятся, а не «на всякий случай».
Ручной ввод
editable включает набор периода одной строкой:
<GrDateRangePicker v-model="period" editable />Строку разбирает не разделитель, а счёт групп цифр: их делят пополам, и каждая половина
разбирается как граница. Поэтому 08/12/2026 — 08/14/2026, 08/12/2026 - 08/14/2026 и
08/12/2026 08/14/2026 — это одно и то же, а en-CA с дефисами внутри самих дат
(2026-08-12 - 2026-08-14) не ломается: список разделителей развалился бы на первой же локали, и
его тут нет.
Половина суток, оказавшаяся между границами (3:30 PM — 8/14/2026), достаётся той границе, к
которой её относит локаль: в en-US она стоит после часа, в ko — перед ним.
Одна дата вместо двух отклоняется. Одна дата — не период; достроить вторую границу значило бы придумать за пользователя то, чего он не задавал.
Обратный порядок нормализуется, как и при кликах: период можно вести назад.
Время принимается ровно тогда, когда пикер его показывает. С enable-time обе границы обязаны
нести время, без него — ни одна: пикер, молча проглотивший 14:30, показал бы значение, которого не
вводили.
Панель идёт за набором. Набранная первая граница подсвечивается началом периода — тем же состоянием, что и после первого клика в сетке; набранная вторая закрывает полосу. Сетка переходит на месяц набранного. Модель при этом не меняется.
Период проверяется теми же правилами, что и выбор мышью: запрещённые даты, min, max,
minRange, maxRange. Разобравшийся, но недопустимый период не применяется и объявляется —
пользователь набрал всё правильно, и молчание выглядело бы как потерянный Enter.
Удачный ввод снимает начало периода, если оно было открыто кликом в панели: иначе следующий клик закрыл бы период от даты, о которой в поле уже ничего не написано.
Как и у GrDateTimePicker, поле с editable показывает границы цифрами — набор обязан читать
обратно показанное, — маски нет, а плейсхолдер по умолчанию показывает обе границы целиком.
Адаптер применяется к каждой границе
Модель — пара, и valueAdapter разбирает и собирает обе её стороны. При
valueAdapter="isoDate" наружу уходит ['2026-08-10', '2026-08-14'], а не одна
строка с разделителем: разделитель пришлось бы выбирать за потребителя, и он же
сломался бы на первой локали, где тире значит что-то другое.
Пропы этого примера