Создание собственного плагина

Механизм расширений в Flatpickr построен вокруг концепции плагинов как функций, модифицирующих поведение экземпляра календаря через стандартный жизненный цикл. Плагин представляет собой чистую функцию, которая получает доступ к экземпляру календаря и возвращает объект с хуками или модификациями состояния.

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

Базовая структура плагина:

function myPlugin(pluginConfig) {
  return function (fp) {
    return {
      onReady() {},
      onChange() {},
      onOpen() {},
      onClose() {},
      onDestroy() {}
    };
  };
}

Плагин регистрируется через опцию plugins:

flatpickr("#input", {
  plugins: [myPlugin({ option: true })]
});

Контракт плагина и взаимодействие с экземпляром календаря

Экземпляр Flatpickr передаётся в плагин как аргумент fp. Этот объект содержит состояние календаря, DOM-структуру, методы управления и внутренние параметры.

Основные области доступа:

  • fp.input — исходный input-элемент
  • fp.calendarContainer — контейнер календаря
  • fp.selectedDates — массив выбранных дат
  • fp.config — конфигурация
  • fp.setDate() — программная установка даты
  • fp.close() / fp.open() — управление видимостью

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

Жизненный цикл плагина

Плагины в Flatpickr синхронизированы с жизненным циклом экземпляра календаря.

Основные хуки:

onReady

Вызывается после полной инициализации календаря и построения DOM.

onReady() {
  this.fp.calendarContainer.classList.add("plugin-ready");
}

Типичное применение:

  • добавление классов
  • модификация DOM
  • инициализация сторонних библиотек

onOpen / onClose

Используются для реагирования на открытие и закрытие календаря.

onOpen() {
  console.log("calendar opened");
}

onClose() {
  console.log("calendar closed");
}

Часто применяются для:

  • анимаций
  • синхронизации UI
  • загрузки данных

onChange

Срабатывает при изменении выбранной даты.

onChange(selectedDates, dateStr) {
  console.log(selectedDates, dateStr);
}

Позволяет реализовать:

  • валидацию
  • кастомную обработку дат
  • синхронизацию с внешними системами

onDestroy

Используется для очистки ресурсов.

onDestroy() {
  this.cleanup?.();
}

Создание плагина с модификацией интерфейса

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

Пример плагина, добавляющего дополнительную панель информации:

function infoPanelPlugin(config) {
  return function(fp) {
    let panel;

    return {
      onReady() {
        panel = document.createElement("div");
        panel.className = "fp-info-panel";
        panel.textContent = config.text || "Выберите дату";

        fp.calendarContainer.appendChild(panel);
      },

      onChange(selectedDates) {
        if (selectedDates.length) {
          panel.textContent = `Выбрано: ${selectedDates[0].toLocaleDateString()}`;
        }
      },

      onDestroy() {
        panel?.remove();
      }
    };
  };
}

Такой подход демонстрирует принцип расширяемости: логика не изменяет ядро, а дополняет DOM-структуру.

Работа с конфигурацией плагина

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

function highlightRangePlugin(config) {
  return function(fp) {
    return {
      onDayCreate(dObj, dStr, fpInstance, dayElem) {
        if (config.highlightWeekends && [0,6].includes(dObj.getDay())) {
          dayElem.classList.add("is-weekend");
        }
      }
    };
  };
}

Передача конфигурации:

flatpickr("#input", {
  plugins: [
    highlightRangePlugin({
      highlightWeekends: true
    })
  ]
});

Хук onDayCreate и модификация отдельных дней

Один из наиболее мощных инструментов расширений в Flatpickr — перехват создания ячеек календаря.

onDayCreate(dObj, dStr, fp, dayElem) {
  if (dObj.getDate() === 1) {
    dayElem.classList.add("first-day");
  }
}

Аргументы:

  • dObj — объект Date текущего дня
  • dStr — строковое представление
  • fp — экземпляр календаря
  • dayElem — DOM-элемент ячейки

Этот хук позволяет реализовать:

  • подсветку диапазонов
  • отметки событий
  • кастомные состояния дней

Взаимодействие с состоянием календаря

Плагин может читать и изменять состояние экземпляра Flatpickr.

Пример синхронизации внешнего состояния:

function syncPlugin(store) {
  return function(fp) {
    return {
      onChange(selectedDates) {
        store.value = selectedDates;
      },

      onReady() {
        if (store.value?.length) {
          fp.setDate(store.value);
        }
      }
    };
  };
}

Ключевой принцип — односторонняя или контролируемая синхронизация, чтобы избежать конфликтов между UI и внешним состоянием.

Добавление собственных методов в экземпляр

Плагины могут расширять API экземпляра календаря Flatpickr, добавляя новые методы.

function extendApiPlugin() {
  return function(fp) {
    fp.jumpToToday = function() {
      fp.setDate(new Date());
      fp.open();
    };

    return {};
  };
}

Использование:

const instance = flatpickr("#input", {
  plugins: [extendApiPlugin()]
});

instance.jumpToToday();

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

Работа с DOM и изоляция логики

Плагины в Flatpickr часто взаимодействуют с DOM напрямую, но должны соблюдать изоляцию:

  • не удалять внутренние элементы календаря без необходимости
  • использовать собственные контейнеры
  • избегать глобальных селекторов

Пример корректной изоляции:

onReady() {
  const wrapper = document.createElement("div");
  wrapper.className = "my-plugin-wrapper";

  wrapper.appendChild(document.createTextNode("Custom UI"));
  this.fp.calendarContainer.appendChild(wrapper);
}

Управление событиями и очистка

Корректное управление событиями критично для предотвращения утечек памяти в Flatpickr.

function eventPlugin() {
  return function(fp) {
    const handler = () => console.log("click");

    return {
      onReady() {
        fp.calendarContainer.addEventListener("click", handler);
      },

      onDestroy() {
        fp.calendarContainer.removeEventListener("click", handler);
      }
    };
  };
}

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

Композиция нескольких плагинов

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

flatpickr("#input", {
  plugins: [
    pluginA(),
    pluginB(),
    pluginC()
  ]
});

Рекомендации по совместимости:

  • избегать модификации одних и тех же DOM-узлов
  • не переопределять одни и те же методы без проверки
  • использовать уникальные namespace-классы

Паттерны проектирования плагинов

В экосистеме Flatpickr часто применяются следующие архитектурные подходы:

Фабрика плагинов

Позволяет создавать конфигурируемые экземпляры:

const createPlugin = (options) => (fp) => ({
  onReady() {
    if (options.enabled) {
      fp.calendarContainer.classList.add("enabled");
    }
  }
});

Декоратор поведения

Расширяет существующую логику:

function decoratorPlugin(fp) {
  const originalSetDate = fp.setDate;

  fp.setDate = function(...args) {
    console.log("date changed");
    return originalSetDate.apply(this, args);
  };

  return {};
}

Event-driven расширение

Основано на реакциях на события календаря:

function eventDrivenPlugin() {
  return function(fp) {
    return {
      onChange(dates) {
        if (dates.length > 1) {
          console.log("range selected");
        }
      }
    };
  };
}

Ошибки проектирования плагинов

При разработке расширений для Flatpickr часто возникают типовые проблемы:

  • утечки памяти из-за неотписанных событий
  • конфликт классов и селекторов
  • повторная инициализация DOM-элементов
  • модификация приватных полей экземпляра
  • нарушение предсказуемости жизненного цикла

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