GrDateRangePicker

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

Берут, когда отчёт за период.

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

Обзор

Когда брать

  • отчёт за период — «с — по» это одно поле и одно значение, а не два независимых: два GrDatePicker рядом не мешают выбрать конец раньше начала;
  • бронирование и занятость — предпросмотр по наведению показывает будущий диапазон до второго клика;
  • фильтр списка по датам — модель это пара, поэтому в query-string она уезжает одним параметром;
  • границы обязаны быть согласованы — компонент сам держит порядок и не выпускает наружу перевёрнутый период.

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

НужноБерите
Одна датаGrDatePicker
Дата со временемGrDateTimePicker
Только времяGrTimePicker
Сетка месяца без поляGrCalendar
Период выбирают только из готовых, свой не нуженGrSegmented

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

  • Период отчёта

    Обе границы или ничего: полувыбранный период значением не считается. Пресеты — функции, поэтому «Last 30 days» считается в момент нажатия; max не пускает в будущее. value-adapter="isoDate" делает каждую границу строкой «2026-09-07» — параметрами запроса отчёта, — а name отправляет их двумя скрытыми полями.

    Revenue report
    GET /v1/reports/revenue?from=2026-09-07&to=2026-10-06
  • Заявка на отпуск по правилам компании

    min-range и max-range — свойства пары, а не даты: ни одна граница не скажет «не больше двух недель». Выходные и праздники — disabled-dates, поэтому заявка на них не начинается и не заканчивается, а страница считает рабочие дни между границами.

    Request leave17 days left this year

    Up to 14 days in one request

    5 working days
  • Окно обслуживания с точностью до четверти часа

    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

PropTypeпо умолчаниюОписание
modelValuev-modelreadonly [TItem, TItem] | null | undefinedundefinedОбе границы или ничего: полупустой диапазон значением не считается.
valueAdapter
"date" | "isoDate" | "isoDateTime" | "timestamp" | GrChronoA"date" | "isoDate" | "isoDateTime" | "timestamp" | GrChronoAdapter<TItem> | undefined
undefinedКак границы уходят наружу и приходят обратно. Адаптер применяется к каждой отдельно, поэтому модель — пара `[T, T]`.
minDate | undefinedundefinedСамая ранняя дата, которую можно выбрать. Дни раньше неё в панели видны, но выключены.
maxDate | undefinedundefinedСамая поздняя дата, которую можно выбрать. Дни позже неё в панели видны, но выключены.
disabledDatesreadonly Date[] | ((date: Date) => boolean) | undefinedundefinedЗапрещённые даты: список или предикат.
enableTimeboolean | undefinedfalseГраницы периода задаются с точностью до минут, а не полуночью. Панель получает две колонки времени — для начала и для конца. Порядок произвольный: даты выбираются кликами, время правится когда угодно. Мастера из четырёх шагов здесь нет намеренно — он завёл бы скрытое состояние «на каком мы шаге», из которого промахнувшегося не вывести иначе как пройдя всё заново.
minuteStepnumber | undefined1Шаг колонки минут в минутах. По умолчанию `1`; осмыслен с `enableTime`.
secondStepnumber | undefined1Шаг колонки секунд в секундах. По умолчанию `1`; осмыслен с `enableSeconds`.
enableSecondsboolean | undefinedfalseКолонка секунд в панели и секунды в поле. По умолчанию выключена.
use12Hoursboolean | undefinedundefinedНе задан — берётся из локали.
minRangenumber | undefinedundefinedНаименьшая длина периода в днях, считая обе границы.
maxRangenumber | undefinedundefinedНаибольшая длина периода в днях, считая обе границы.
presetsreadonly GrDateRangePreset[] | undefinedundefinedГотовые периоды в подвале панели: «Сегодня», «Последние 7 дней». Границы можно задать функцией — «последние 7 дней» отсчитываются от сегодняшнего дня, а не от дня, когда объявили проп. Функция обязана быть чистой: её зовут и чтобы решить, доступен ли шорткат, и чтобы применить его. Период вне `min`/`max`, задевающий `disabledDates` или нарушающий `minRange`/`maxRange`, приходит выключенным: кнопка, которая ничего не делает, обманывает.
weekStartIsoWeekday | undefinedundefinedПервый день недели по ISO (1 — понедельник). Не задан — из локали.

Слоты

SlotTypeОписание
day{ cell: CalendarCell; selected: boolean; }Своя ячейка дня вместо числа.
header{ title: string; goToPeriod: (delta: number) => void; }Своя шапка сетки вместо заголовка и стрелок.
weekday{ label: string; full: string; isoWeekday: IsoWeekday; }Своя ячейка шапки недели вместо сокращённого названия дня.
footer
{ setRange: (from: Date, to: Date) => boolean; canSetRange: { setRange: (from: Date, to: Date) => boolean; canSetRange: (from: Date, to: Date) => boolean; close: () => void; }
Подвал панели. Выбор периода отдаётся внутрь: шорткат обязан выставить **обе** границы и не обойти `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

grDateRangePickerConfigconst

Панель собрана из чужой разметки: сетка — GrCalendar, колонки времени — внутренний компонент GrTimePicker, поповер и кнопки — ядро. Все рёбра объявлены, иначе потребитель, выбравший один GrDateRangePicker, получил бы панель без стилей. Ребро к GrTimePicker безусловно, хотя колонки включает необязательный enable-time: селекция статична, и по ней не видно, каким пропом компонент будет вызван, — а импорт колонок в собранном чанке есть всегда.

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

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

GrDateRangePickerConfigurablePropsinterface

Пропы GrDateRangePicker, настраиваемые глобально через <GrConfigProvider :component-defaults="{ GrDateRangePicker: { … } }">.

interface GrDateRangePickerConfigurableProps {
    size: GrDateRangePickerSize;
    clearable: boolean;
    placement: UseFloatingPlacement;
}

Файлdist/types/components/GrDateRangePicker/grDateRangePickerStyles.d.ts

GrDateRangePickerSizetype

Публичное имя типа размера компонента. Классы поля общие для всех пикеров пакета, полосу диапазона рисует GrCalendar — своих классов у пикера нет.

type GrDateRangePickerSize = GrPickerSize;

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

grDateRangePickerSafelistconst
const grDateRangePickerSafelist: string[];

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

GrDateRangePickerPropsinterface

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);
GrDateRangePickerEmitsinterface
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;
}
GrDateRangePresetinterface

Готовый период в подвале панели.

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 добавляет в панель две колонки времени — для начала и для конца:

vue
<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 включает набор периода одной строкой:

vue
<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'], а не одна строка с разделителем: разделитель пришлось бы выбирать за потребителя, и он же сломался бы на первой локали, где тире значит что-то другое.