Синхронизация нескольких экземпляров

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

Каждый экземпляр Flatpickr изолирован и управляет собственным DOM-элементом, однако предоставляет механизмы внешнего управления через API. Основные точки синхронизации:

  • события жизненного цикла (onChange, onOpen, onClose)
  • программные методы (setDate, clear, set, jumpToDate)
  • динамическое изменение конфигурации (config)
  • общие внешние состояния (store, переменные, состояния фреймворков)

Ключевой принцип заключается в том, что один экземпляр выступает источником изменений, а остальные — реактивными получателями.

Базовая синхронизация через onChange

Наиболее прямолинейный способ синхронизации — использование обработчика onChange, который распространяет изменения на связанные экземпляры.

const fpStart = flatpickr("#start", {
  onChange: function(selectedDates) {
    fpEnd.set("minDate", selectedDates[0]);
  }
});

const fpEnd = flatpickr("#end", {
  onChange: function(selectedDates) {
    fpStart.set("maxDate", selectedDates[0]);
  }
});

В этой схеме каждый календарь ограничивает допустимый диапазон другого. Важно учитывать, что изменение minDate и maxDate не вызывает onChange, что предотвращает циклические обновления.

Двусторонняя синхронизация диапазона дат

При реализации диапазона дат требуется согласованное управление двумя значениями: началом и концом интервала. Более строгая модель предполагает централизованное состояние.

const state = {
  start: null,
  end: null
};

const fpStart = flatpickr("#start", {
  onChange: (dates) => {
    state.start = dates[0];

    fpEnd.set("minDate", state.start);

    if (state.end && state.end < state.start) {
      state.end = null;
      fpEnd.clear();
    }
  }
});

const fpEnd = flatpickr("#end", {
  onChange: (dates) => {
    state.end = dates[0];
    fpStart.set("maxDate", state.end);
  }
});

Такая структура устраняет рассинхронизацию логических границ диапазона и обеспечивает корректную валидацию порядка дат.

Предотвращение циклических обновлений

При двусторонней синхронизации возникает риск бесконечных циклов обновления. Flatpickr не вызывает события при изменении ограничений, однако программные вызовы setDate могут провоцировать повторные триггеры.

Решение заключается в использовании флагов блокировки:

let syncing = false;

const fpA = flatpickr("#a", {
  onChange: (dates) => {
    if (syncing) return;
    syncing = true;

    fpB.setDate(dates[0], false);
    syncing = false;
  }
});

const fpB = flatpickr("#b", {
  onChange: (dates) => {
    if (syncing) return;
    syncing = true;

    fpA.setDate(dates[0], false);
    syncing = false;
  }
});

Второй аргумент setDate(..., false) отключает триггер события onChange, снижая риск каскадных обновлений.

Синхронизация через общий внешний стор

При увеличении количества экземпляров локальная логика становится неуправляемой. Централизованное состояние упрощает архитектуру.

const store = {
  value: null,
  subscribers: []
};

function setValue(date) {
  store.value = date;
  store.subscribers.forEach(fn => fn(date));
}

function subscribe(fn) {
  store.subscribers.push(fn);
}

const fp1 = flatpickr("#input1", {
  onChange: (d) => setValue(d[0])
});

const fp2 = flatpickr("#input2", {
  onChange: (d) => setValue(d[0])
});

subscribe((date) => {
  fp1.setDate(date, false);
  fp2.setDate(date, false);
});

Такой подход превращает Flatpickr-инстансы в UI-слои поверх единого состояния, устраняя прямые зависимости между ними.

Синхронизация диапазонов с общими ограничениями

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

const globalLimits = {
  min: new Date(2024, 0, 1),
  max: new Date(2026, 11, 31)
};

const instances = document.querySelectorAll(".date");

instances.forEach((el) => {
  flatpickr(el, {
    minDate: globalLimits.min,
    maxDate: globalLimits.max
  });
});

При изменении глобальных ограничений необходимо обновлять все экземпляры через set:

instances.forEach((instance) => {
  instance._flatpickr.set("minDate", new Date(2025, 0, 1));
});

Использование внутреннего свойства _flatpickr позволяет получить доступ к API, но требует аккуратности, так как не является частью DOM-абстракции.

Связанные календарные группы (master-detail)

В интерфейсах выбора периода часто применяется модель “главный календарь — зависимые календари”.

const master = flatpickr("#master", {
  onChange: (dates) => {
    const base = dates[0];

    detail1.setDate(base, false);
    detail2.setDate(new Date(base.getTime() + 86400000), false);
  }
});

const detail1 = flatpickr("#d1");
const detail2 = flatpickr("#d2");

Здесь один источник определяет базовую дату, а остальные экземпляры вычисляют производные значения.

Синхронизация конфигураций в реальном времени

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

function syncConfig(instances, config) {
  instances.forEach((i) => {
    i.set(config);
  });
}

syncConfig([fp1, fp2], {
  dateFormat: "Y-m-d",
  enableTime: true,
  time_24hr: true
});

Изменение конфигурации применяется мгновенно без пересоздания экземпляров, что критично для динамических интерфейсов.

Синхронизация с задержкой и батчинг изменений

При массовых обновлениях нескольких экземпляров важно избегать лишних перерасчётов DOM и повторных рендеров.

function batchUpdate(instances, date) {
  requestAnimationFrame(() => {
    instances.forEach((i) => i.setDate(date, false));
  });
}

Использование requestAnimationFrame объединяет обновления в один цикл отрисовки, снижая нагрузку.

Управление зависимыми диапазонами в сложных формах

В сложных формах часто присутствует цепочка зависимых дат:

  • дата начала проекта
  • промежуточные контрольные точки
  • дата завершения

Каждый элемент зависит от предыдущего.

start.onCha nge = (dates) => {
  const d = dates[0];

  milestone.set("minDate", d);
  end.set("minDate", d);
};

milestone.onCha nge = (dates) => {
  const d = dates[0];

  end.set("minDate", d);
};

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

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

Flatpickr можно рассматривать как источник событий, которые формируют поток данных.

function link(instances) {
  instances.forEach((source) => {
    source.config.onChange.push((dates) => {
      instances.forEach((target) => {
        if (target !== source) {
          target.setDate(dates[0], false);
        }
      });
    });
  });
}

Подход превращает систему в событийную сеть, где каждый экземпляр реагирует на изменения других без жёсткой иерархии.

Особенности синхронизации времени

При включённом enableTime синхронизация усложняется из-за необходимости учитывать часы и минуты. Прямое копирование даты может приводить к неожиданным смещениям.

function syncDateTime(from, to) {
  const d = new Date(from.selectedDates[0]);

  to.setDate(d, false);
}

При необходимости нормализации времени используется явная установка компонентов даты через setHours, setMinutes, setSeconds.


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