Система плагинов в Tom Sel ect строится вокруг расширяемой архитектуры, в которой каждый плагин представляет собой изолированный модуль, подключаемый к экземпляру селекта и способный модифицировать его поведение, DOM-структуру, события и внутреннее состояние. При использовании TypeScript ключевую роль играет корректная типизация точек расширения, поскольку плагины фактически становятся частью жизненного цикла компонента.
Базовая модель плагина в типах описывается как функция, принимающая экземпляр Tom Select и возвращающая void либо набор хуков:
export type TomSelectPlugin = (instance: TomSelect) => void;
Однако в реальной типизации этого недостаточно, поскольку плагины могут:
Поэтому полноценная типизация строится вокруг расширения интерфейса экземпляра и декларации модуля.
Каждый плагин в TypeScript контексте рассматривается как функция-инициализатор, которая получает ссылку на экземпляр и работает с ним через публичный интерфейс:
interface TomSelect {
on(event: string, handler: (...args: any[]) => void): void;
off(event: string, handler: (...args: any[]) => void): void;
addItem(value: string): void;
removeItem(value: string): void;
refreshOptions(triggerDropdown: boolean): void;
}
Плагин использует этот интерфейс как точку входа, но часто расширяет его:
function myPlugin(ts: TomSelect) {
ts.on('change', () => {
// логика плагина
});
}
Ключевая особенность типизации плагинов заключается в возможности расширения самого экземпляра Tom Select. Это делается через declaration merging.
declare module 'tom-select' {
interface TomSelect {
highlightActive: () => void;
}
}
После этого плагин может безопасно добавлять методы:
function highlightPlugin(ts: TomSelect) {
ts.highlightActive = function () {
const active = ts.dropdown.querySelector('.active');
if (active) active.classList.add('highlight');
};
}
Такой подход обеспечивает:
anyБолее строгий уровень типизации достигается через дженерики, позволяющие описывать расширение экземпляра:
type Plugin<T = {}> = (instance: TomSelect & T) => void;
Использование:
type HighlightExtension = {
highlightActive: () => void;
};
const highlightPlugin: Plugin<HighlightExtension> = (ts) => {
ts.highlightActive = () => {
ts.refreshOptions(true);
};
};
Такой подход позволяет явно фиксировать контракт расширений, что особенно важно в больших системах с множеством плагинов.
Плагины активно используют событийную модель Tom Select. Типизация событий является критически важной частью, поскольку без неё теряется безопасность аргументов.
Базовый вариант:
type TomSelectEvent =
| 'initialize'
| 'change'
| 'item_add'
| 'item_remove'
| 'dropdown_open'
| 'dropdown_close';
Расширенный вариант с маппингом:
interface TomSelectEvents {
change: (value: string) => void;
item_add: (value: string, item: HTMLElement) => void;
item_remove: (value: string) => void;
dropdown_open: () => void;
dropdown_close: () => void;
}
Тогда плагин может использовать строго типизированную подписку:
function typedPlugin(ts: TomSelect) {
ts.on('item_add', (value: string, item: HTMLElement) => {
item.classList.add('plugin-added');
});
}
Tom Select позволяет подключать плагины через конфигурационный
объект. В типах это выражается через поле plugins, которое
может быть строковым списком или словарём функций:
interface TomSelectOptions {
plugins?: string[] | Record<string, TomSelectPlugin>;
}
Расширенный вариант с типизацией:
type PluginMap = Record<string, TomSelectPlugin>;
interface TomSelectOptions {
plugins?: (keyof PluginMap)[] | PluginMap;
}
Это позволяет:
Встроенные плагины Tom Select также требуют описания контрактов,
поскольку они расширяют поведение компонента. Например, плагин
remove_button добавляет кнопки удаления элементов.
Типизация такого расширения:
interface RemoveButtonPluginOptions {
title?: string;
label?: string;
}
И соответствующее расширение экземпляра:
declare module 'tom-select' {
interface TomSelect {
removeButton: {
createButton: (item: HTMLElement) => HTMLElement;
};
}
}
Некоторые плагины сохраняют внутреннее состояние, которое должно быть типизировано отдельно от экземпляра:
interface PluginState {
enabled: boolean;
cache: Map<string, HTMLElement>;
}
Использование внутри плагина:
function statefulPlugin(ts: TomSelect) {
const state: PluginState = {
enabled: true,
cache: new Map()
};
ts.on('item_add', (value, item) => {
state.cache.set(value, item);
});
}
Такой подход предотвращает утечки типов в глобальный контекст и изолирует данные плагина.
При комбинировании нескольких плагинов возникает риск конфликтов расширений экземпляра. TypeScript решает это через объединение интерфейсов, но логически требуется учитывать пересечения.
interface TomSelect {
highlightActive(): void;
highlightActive(): void; // конфликт сигнатур невозможен
}
Конфликт возникает при несовместимых расширениях, поэтому используется стратегия пространств имён:
declare module 'tom-select' {
interface TomSelect {
highlightPlugin: {
highlightActive: () => void;
};
}
}
Это позволяет изолировать расширения и избежать коллизий.
Для крупных систем используется фабричный подход, при котором плагин создаётся как функция с конфигурацией:
type PluginFactory<TOptions = {}> = (options: TOptions) => TomSelectPlugin;
Пример:
interface HighlightOptions {
className: string;
}
const createHighlightPlugin: PluginFactory<HighlightOptions> =
(options) => (ts) => {
ts.on('item_add', (_, item) => {
item.classList.add(options.className);
});
};
Этот подход позволяет:
Некоторые реализации требуют возможности отключения плагина. Для этого вводится контракт cleanup-функции:
type PluginDestroy = () => void;
type TomSelectPlugin = (instance: TomSelect) => PluginDestroy | void;
Пример:
const plugin: TomSelectPlugin = (ts) => {
const handler = () => {
ts.refreshOptions(false);
};
ts.on('change', handler);
return () => {
ts.off('change', handler);
};
};
Такой механизм обеспечивает:
При масштабных проектах плагины оформляются как отдельные модули с собственными декларациями:
// highlight-plugin.d.ts
declare module 'tom-select/plugins/highlight' {
const plugin: TomSelectPlugin;
export default plugin;
}
И использование:
import highlight fr om 'tom-select/plugins/highlight';
Это позволяет:
Типичный хорошо типизированный плагин в экосистеме Tom Select включает:
TomSelectPluginТакая структура обеспечивает предсказуемую интеграцию в систему, где каждый плагин становится формально описанным расширением базового компонента, а не неявной модификацией поведения.