Валидация пользовательского ввода

Работа с датами в интерфейсе неизбежно включает два источника данных: выбор через календарь и ручной ввод в текстовое поле. Даже при использовании календарного UI пользовательский ввод остаётся частью общей модели данных, и именно он чаще всего становится причиной некорректных значений, ошибок парсинга и логических несоответствий. Валидация в этом контексте строится на нескольких уровнях: ограничение допустимых диапазонов дат, контроль формата, обработка частично введённых значений и синхронизация состояния между инпутом и календарём.


Модель данных и источники некорректных значений

Pikaday работает с объектами Date, однако текстовое поле остаётся внешним представлением этого состояния. Расхождение возникает в следующих случаях:

  • ввод строки, не соответствующей формату парсинга;
  • указание даты вне допустимого диапазона;
  • частичное или незавершённое значение (например, 2026-..-..);
  • конфликт локализации формата (день/месяц);
  • программная подмена значения поля без обновления инстанса календаря.

Базовая задача валидации заключается в приведении всех этих сценариев к единому состоянию Date | null.


Ограничение диапазона дат

Одним из основных механизмов валидации в Pikaday являются параметры minDate и maxDate.

const picker = new Pikaday({
  field: document.getElementById('date'),
  minDate: new Date(2020, 0, 1),
  maxDate: new Date(2030, 11, 31)
});

Эти ограничения влияют на:

  • доступность дат в UI календаря;
  • возможность выбора через клик;
  • автоматическую блокировку навигации по недопустимым месяцам.

Однако важно учитывать, что прямой ввод в поле не блокируется автоматически. Это означает, что значение может быть установлено программно или введено вручную, минуя ограничения UI.


Пост-валидация через событие onSelect

Событие onSelect используется как точка подтверждения выбора даты. Оно не предотвращает ввод некорректных данных, но позволяет немедленно их проверять.

const picker = new Pikaday({
  field: document.getElementById('date'),
  onSelect: function(date) {
    if (date < this.getStartRange?.() || date > this.getEndRange?.()) {
      this.setDate(null);
    }
  }
});

Практически применяемый сценарий — проверка бизнес-правил, выходящих за пределы min/max диапазона:

  • запрет выходных;
  • проверка рабочих дней;
  • ограничение по возрасту;
  • зависимость от других полей формы.

Фильтрация недопустимых дней через disableDayFn

Механизм disableDayFn обеспечивает более тонкую валидацию, чем фиксированные границы диапазона.

const picker = new Pikaday({
  field: document.getElementById('date'),
  disableDayFn: function(date) {
    const day = date.getDay();
    return day === 0 || day === 6;
  }
});

Функция возвращает true для дат, которые должны быть недоступны. Это влияет на:

  • визуальную блокировку дат в календаре;
  • невозможность выбора через UI;
  • предотвращение кликов по невалидным значениям.

Однако ручной ввод остаётся потенциальным обходом, что требует дополнительной проверки.


Валидация текстового ввода

Ручной ввод в поле является основным источником неконсистентных данных. Pikaday не навязывает строгий формат парсинга, поэтому ответственность за проверку лежит на интеграции.

Типичный подход — обработка события blur:

const field = document.getElementById('date');

field.addEventListener('blur', () => {
  const value = field.value;
  const parsed = new Date(value);

  if (isNaN(parsed.getTime())) {
    picker.setDate(null);
    field.classList.add('invalid');
  } else {
    picker.setDate(parsed);
  }
});

Основная проблема такого подхода — неоднозначность форматов. Строка 01/02/2026 может интерпретироваться как:

  • 1 февраля;
  • 2 января.

Контроль формата через кастомный парсер

Более устойчивый подход заключается в явном контроле парсинга:

function parseDate(value) {
  const [day, month, year] = value.split('.');
  const date = new Date(year, month - 1, day);

  if (date.getFullYear() != year ||
      date.getMonth() != month - 1 ||
      date.getDate() != day) {
    return null;
  }

  return date;
}

Использование строгого разбора устраняет частично валидные значения, такие как 31.02.2026, которые стандартный Date автоматически корректирует.


Синхронизация состояния поля и календаря

Расхождение между инпутом и состоянием Pikaday возникает при:

  • программном изменении значения поля;
  • внешнем обновлении формы;
  • сбросе состояния без уведомления календаря.

Корректная синхронизация требует явного управления:

function setValidDate(date) {
  picker.setDate(date, true);
  field.value = date ? format(date) : '';
}

Второй параметр true предотвращает повторный вызов onSelect, что исключает циклические обновления.


Ограничение ввода на уровне UI

HTML-атрибуты используются как дополнительный слой защиты:

<input type="text" pattern="\d{2}\.\d{2}\.\d{4}" />

Однако pattern не заменяет программную проверку, поскольку:

  • легко обходится через JavaScript;
  • не контролирует вставку через clipboard;
  • не влияет на программное изменение value.

Обработка частично введённых значений

Промежуточные состояния ввода требуют отдельного подхода. Значение может быть временно невалидным во время набора:

  • 1
  • 12.
  • 12.0
  • 12.01.2

Жёсткая валидация на каждом input событии приводит к деградации UX. Вместо этого используется отложенная проверка:

let timeout;

field.addEventListener('input', () => {
  clearTimeout(timeout);

  timeout = setTimeout(() => {
    const date = parseDate(field.value);

    if (date) {
      picker.setDate(date);
    }
  }, 300);
});

Валидация зависимых дат

В сценариях диапазонов (start/end date) возникает необходимость межполейной проверки:

const start = new Pikaday({ field: startField });
const end = new Pikaday({ field: endField });

start.config({
  maxDate: end.getDate()
});

end.config({
  minDate: start.getDate()
});

Логическая валидация дополняется контролем при каждом изменении одного из полей.


Защита от неконсистентного состояния

Неконсистентность возникает при:

  • изменении значения без обновления календаря;
  • ручной установке невалидной даты;
  • несоответствии локали формату.

Устойчивый подход заключается в централизованной функции состояния:

function updateDate(value) {
  const date = parseDate(value);

  if (!date || date < min || date > max) {
    picker.setDate(null, true);
    field.value = '';
    return;
  }

  picker.setDate(date, true);
}

Итоговая модель валидации

Валидация в Pikaday-интеграции строится как многоуровневая система:

  • UI-ограничения (minDate, maxDate, disableDayFn);
  • контроль выбора через onSelect;
  • строгий парсинг текстового ввода;
  • синхронизация состояния календаря и поля;
  • отложенная проверка пользовательского ввода;
  • межполевая логическая проверка.

Каждый уровень компенсирует ограничения предыдущего, формируя устойчивую модель обработки дат, в которой ни один источник ввода не рассматривается как полностью доверенный.