Переопределение CSS классов

Tom Select строит интерфейс вокруг набора заранее определённых CSS-классов, которые присваиваются ключевым DOM-узлам: контейнеру, полю ввода, выпадающему списку, выбранным элементам и состояниям (активный, disabled, выбранный). Архитектура библиотеки предполагает два уровня управления внешним видом: через стандартные классы по умолчанию и через систему переопределения classNames, позволяющую полностью заменить или расширить разметочную семантику.

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

  • ts-wrapper — обёртка компонента
  • ts-control — зона ввода и отображения выбранных значений
  • ts-dropdown — выпадающий список
  • option — элементы списка
  • item — выбранные значения
  • active, selected, disabled — состояния элементов

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

Переопределение через параметр classNames

Основной механизм кастомизации в Tom Select реализуется через объект classNames, который передаётся в конфигурации экземпляра:

new TomSelect('#select', {
  classNames: {
    wrapper: 'my-wrapper',
    control: 'my-control',
    dropdown: 'my-dropdown',
    dropdownContent: 'my-dropdown-content',
    input: 'my-input',
    item: 'my-item',
    option: 'my-option',
    active: 'is-active',
    selected: 'is-selected',
    disabled: 'is-disabled',
    focus: 'is-focused',
    loading: 'is-loading'
  }
});

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

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

Принципы приоритета классов

CSS в контексте Tom Select подчиняется стандартной каскадной модели, где итоговый стиль зависит от:

  1. специфичности селектора
  2. порядка подключения CSS
  3. наличия конфликтующих правил
  4. использования !important

Поскольку библиотека использует фиксированные классы, переопределение часто требует более специфичных селекторов:

.my-control.ts-control {
  border: 2px solid #3b82f6;
  border-radius: 8px;
}

Комбинирование пользовательского класса и встроенного класса повышает приоритет без необходимости использовать !important.

Изменение структуры через wrapper

Класс wrapper играет ключевую роль, поскольку оборачивает весь компонент. Это позволяет задавать глобальную тему для конкретного экземпляра:

new TomSelect('#select', {
  classNames: {
    wrapper: 'select-theme-dark'
  }
});

Далее стили могут быть полностью изолированы:

.select-theme-dark .ts-control {
  background: #111827;
  color: #f9fafb;
}

.select-theme-dark .ts-dropdown {
  background: #1f2937;
  border-color: #374151;
}

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

Переопределение состояния элементов

Состояния элементов управляются через отдельные классы, что позволяет точно контролировать поведение интерфейса:

  • active — элемент под курсором
  • selected — выбранное значение
  • disabled — недоступный элемент
  • focus — фокус ввода
  • loading — состояние загрузки

Пример кастомизации состояния:

.is-active {
  background-color: rgba(59, 130, 246, 0.15);
}

.is-selected {
  background-color: #2563eb;
  color: white;
}

.is-disabled {
  opacity: 0.4;
  pointer-events: none;
}

Использование собственных классов через classNames позволяет полностью заменить стандартные имена:

new TomSelect('#select', {
  classNames: {
    active: 'state-active',
    selected: 'state-selected'
  }
});

Изоляция стилей и предотвращение конфликтов

При интеграции в крупные проекты ключевой проблемой становится пересечение глобальных CSS-правил. Tom Select не использует Shadow DOM, поэтому изоляция достигается через:

  • уникальные wrapper-классы
  • неймспейсы в classNames
  • BEM-подобную структуру
  • повышение специфичности

Пример неймспейса:

new TomSelect('#select', {
  classNames: {
    wrapper: 'app-select app-select--primary'
  }
});
.app-select.app-select--primary .ts-control {
  border-color: #6366f1;
}

Такой подход снижает риск коллизий с UI-библиотеками и глобальными reset-стилями.

Полное переопределение визуального слоя

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

new TomSelect('#select', {
  classNames: {
    wrapper: 'custom-select',
    control: 'custom-control',
    dropdown: 'custom-dropdown',
    option: 'custom-option',
    item: 'custom-item',
    input: 'custom-input'
  }
});

В этом случае встроенные стили Tom Select становятся вторичными, а визуальная система строится поверх пользовательской архитектуры CSS.

Работа с динамическими состояниями

Tom Select активно изменяет классы в runtime в зависимости от пользовательских действий. Это требует учёта динамики при проектировании стилей.

Пример поведения:

  • при открытии dropdown добавляется dropdown-active
  • при вводе текста активируется input-active
  • при выборе значения обновляется has-items

Кастомизация:

.ts-wrapper.has-items .ts-control {
  padding-top: 6px;
  padding-bottom: 6px;
}

Динамические классы позволяют строить адаптивные интерфейсы без необходимости вмешательства в JavaScript-логику.

Комбинация classNames и внешних CSS-фреймворков

При использовании Tailwind, Bootstrap или аналогичных систем часто возникает задача согласования классов.

Подходы:

Добавление утилитарных классов через wrapper

new TomSelect('#select', {
  classNames: {
    wrapper: 'tw-relative tw-w-full'
  }
});

Стилизация через встроенные состояния

.ts-control.is-focused {
  outline: 2px solid #60a5fa;
}

Избежание конфликтов с reset-стилями

Некоторые фреймворки обнуляют стили input, что требует явного восстановления:

.ts-control input {
  all: unset;
  width: 100%;
}

Расширение через кастомные CSS-модификаторы

Дополнительный уровень гибкости достигается через введение модификаторов состояния на уровне wrapper:

new TomSelect('#select', {
  onInitialize() {
    this.wrapper.classList.add('select--initialized');
  }
});
.select--initialized .ts-dropdown {
  opacity: 1;
  transform: translateY(0);
}

Такой подход используется для анимаций появления и сложных переходов состояний.

Контроль специфичности и архитектуры CSS

При масштабировании проекта важно избегать хаотичного наращивания специфичности. Рекомендуется:

  • использовать единый namespace для компонента
  • минимизировать использование !important
  • избегать глубокой вложенности селекторов
  • разделять базовые и модифицирующие стили

Пример структурированного CSS:

.select .ts-control { }
.select.is-focused .ts-control { }
.select.is-disabled .ts-control { }

Такая схема обеспечивает предсказуемое поведение при изменении classNames и расширении функциональности компонента.