Визуализация выбранных элементов

Базовая модель отображения выбранного значения

В стандартной конфигурации Awesomplete выбранный элемент подставляется в <input> как строковое значение. Такой подход подходит для одиночного выбора, но становится ограничением при работе с множественными значениями и сложной визуализацией.

Типичный сценарий:

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

Для расширенной визуализации требуется отделить:

  • хранилище данных
  • визуальные элементы выбора
  • механизм ввода и фильтрации

Переход к модели «токенов» (tags/chips)

Множественный выбор реализуется через преобразование выбранных значений в отдельные визуальные блоки.

Структура интерфейса:

<div class="awesomplete-wrapper">
  <div id="selected-container" class="selected-container"></div>
  <input id="input" />
</div>

Каждое выбранное значение отображается как отдельный элемент:

<span class="token">
  JavaScript
  <button class="remove" data-value="JavaScript">×</button>
</span>

Ключевая идея:

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

Синхронизация Awesomplete с внешним контейнером

Awesomplete продолжает отвечать за автодополнение, но вставка значения перехватывается.

const input = document.getElementById("input");
const container = document.getElementById("selected-container");

const awesomplete = new Awesomplete(input, {
  list: ["JavaScript", "TypeScript", "Python", "Go"],
});

Перехват выбора:

input.addEventListener("awesomplete-selectcomplete", function (e) {
  addToken(e.text.value);
  input.value = "";
});

Построение визуального элемента (token rendering)

Функция визуализации выбранного элемента:

function addToken(value) {
  if (isAlreadySelected(value)) return;

  const token = document.createElement("span");
  token.className = "token";

  token.innerHTML = `
    <span class="token-text">${value}</span>
    <button class="remove" data-value="${value}">×</button>
  `;

  container.appendChild(token);
}

Состояние предотвращения дублей:

const selected = new Set();

function isAlreadySelected(value) {
  return selected.has(value);
}

Обновление состояния:

function addToken(value) {
  if (selected.has(value)) return;

  selected.add(value);
  renderToken(value);
}

Удаление выбранных элементов

Удаление должно синхронизироваться с внутренним состоянием:

container.addEventListener("click", function (e) {
  if (e.target.classList.contains("remove")) {
    const value = e.target.dataset.value;
    selected.delete(value);

    e.target.parentElement.remove();
  }
});

Важный аспект:

  • DOM удаляется отдельно
  • состояние Set обновляется независимо
  • Awesomplete не участвует в управлении выбранными элементами

Визуальное оформление токенов

CSS-структура определяет читаемость и UX:

.selected-container {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.token {
  display: inline-flex;
  align-items: center;
  padding: 4px 8px;
  background: #e0e0e0;
  border-radius: 12px;
  font-size: 14px;
}

.token .remove {
  margin-left: 6px;
  border: none;
  background: transparent;
  cursor: pointer;
}

Визуально важные моменты:

  • перенос строк при переполнении
  • компактная форма элементов
  • различимая зона удаления

Связь визуализации с реальными данными формы

При отправке формы Awesomplete не хранит структуру выбранных элементов, поэтому требуется отдельное поле:

<input type="hidden" name="tags" id="tags-hidden" />

Синхронизация:

function syncHiddenInput() {
  document.getElementById("tags-hidden").value =
    Array.from(selected).join(",");
}

Обновление при каждом изменении:

function addToken(value) {
  if (selected.has(value)) return;

  selected.add(value);
  renderToken(value);
  syncHiddenInput();
}
function removeToken(value) {
  selected.delete(value);
  syncHiddenInput();
}

Кастомизация отображения элементов списка Awesomplete

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

Переопределение item:

awesomplete.item = function (text, input) {
  const li = document.createElement("li");

  li.innerHTML = `
    <span class="suggestion-value">${text}</span>
    <span class="hint">добавить</span>
  `;

  return li;
};

Такой подход позволяет:

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

Подсветка уже выбранных значений в списке

Чтобы избежать повторного выбора, элементы списка могут визуально помечаться:

awesomplete.filter = function (text, input) {
  return !selected.has(text) && Awesomplete.FILTER_CONTAINS(text, input);
};

Дополнительная визуальная маркировка:

awesomplete.item = function (text) {
  const li = document.createElement("li");

  if (selected.has(text)) {
    li.classList.add("disabled");
  }

  li.textContent = text;
  return li;
};

CSS:

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

Интеграция визуального состояния с клавиатурной навигацией

Awesomplete использует стрелочную навигацию по <li> элементам. Для улучшения визуальной обратной связи добавляется состояние активного элемента:

li[aria-selected="true"] {
  background: #007acc;
  color: white;
}

Это обеспечивает:

  • визуальное выделение текущего элемента
  • синхронизацию с клавиатурным фокусом
  • предсказуемость выбора

Отрисовка сложных объектов вместо строк

При использовании объектов данные отделяются от отображения:

const list = [
  { label: "JavaScript", id: 1 },
  { label: "TypeScript", id: 2 }
];

Настройка Awesomplete:

new Awesomplete(input, {
  list,
  label: "label",
  value: "id"
});

Визуализация:

awesomplete.item = function (item) {
  const li = document.createElement("li");
  li.textContent = item.label;
  return li;
};

Добавление токена:

function addToken(item) {
  selected.add(item.id);
}

Гибридная модель отображения: input + chips + dropdown

Финальная структура визуализации объединяет три слоя:

  • input — ввод и фильтрация
  • dropdown — список предложений Awesomplete
  • tokens — визуализация выбранных значений

Связь между слоями строится через:

  • события awesomplete-selectcomplete
  • кастомные фильтры
  • внешнее состояние (Set / Array)

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