Сортировка подсказок в Awesomplete по умолчанию основана на сравнении строк без учета языковых особенностей, что приводит к заметным искажениям при работе с кириллицей, диакритикой и различными алфавитами. Базовая логика сравнения ориентирована на ASCII-подобные строки, поэтому порядок элементов часто выглядит случайным с точки зрения пользователя, особенно в многоязычных интерфейсах.
Внутри Awesomplete процесс формирования списка проходит через два
ключевых этапа: фильтрацию и сортировку. После того как набор данных
проходит через filter, применяется функция
sort, которая по умолчанию выглядит как простое
лексикографическое сравнение:
sort: function(a, b) {
return (a.label || a).localeCompare(b.label || b);
}
На практике это уже лучше, чем обычное сравнение оператором
>, но поведение localeCompare без
параметров не гарантирует корректной локализации. В частности:
В реальных интерфейсах список автодополнения почти всегда содержит:
Без учета локали сортировка превращается в технический порядок кодовых точек Unicode, что нарушает ожидаемую когнитивную модель пользователя.
Например, для русского языка корректный порядок:
Аарон
Алексей
Борис
Виктор
Глеб
Но при стандартной сортировке возможны смещения, особенно при наличии латинских символов или вариативных регистров.
Первый уровень исправления — явное указание локали:
const compareRu = (a, b) =>
(a.label || a).localeCompare(b.label || b, 'ru');
Это уже включает правила русского языка, включая:
ё, е;Однако даже этого недостаточно для сложных интерфейсов.
Для унификации сортировки обычно добавляют параметры чувствительности:
const compareRu = (a, b) =>
(a.label || a).localeCompare((b.label || b), 'ru', {
sensitivity: 'base'
});
Параметр sensitivity: 'base' отключает различие
между:
а и А;е и ё (частично, зависит от движка);Это делает сортировку более предсказуемой в пользовательских интерфейсах.
Более производительный и гибкий подход — использование
Intl.Collator. Он создается один раз и затем
переиспользуется, что критично при больших списках.
const collator = new Intl.Collator('ru', {
sensitivity: 'base',
numeric: true
});
Параметр numeric: true добавляет естественную сортировку
чисел внутри строк:
item 2
item 10
item 20
без превращения в:
item 10
item 2
item 20
Awesomplete позволяет переопределять поведение сортировки через опцию
sort. Это основной механизм подключения локализованной
логики.
const collator = new Intl.Collator('ru', {
sensitivity: 'base',
numeric: true
});
new Awesomplete(input, {
list: data,
sort: function(a, b) {
return collator.compare(a.label || a, b.label || b);
}
});
В этом варианте сортировка становится:
localeCompare.В реальных сценариях элементы списка редко являются строками. Чаще используются структуры:
[
{ label: "Москва", value: "MSK" },
{ label: "Магадан", value: "MAG" },
{ label: "Минск", value: "MSQ" }
]
В этом случае важно явно выделить поле сортировки:
const collator = new Intl.Collator('ru', {
sensitivity: 'base'
});
function sortCities(a, b) {
return collator.compare(a.label, b.label);
}
И подключение:
new Awesomplete(input, {
list: cities,
sort: sortCities
});
При наличии мультиязычных данных (например, русский + английский) появляется дополнительная проблема: выбор базовой локали.
Типичный подход:
const collator = new Intl.Collator(undefined, {
sensitivity: 'base'
});
Передача undefined заставляет браузер использовать
системную локаль пользователя. Это дает более естественный порядок для
конечного пользователя, но снижает предсказуемость на серверной
стороне.
Альтернативный подход — фиксированная локаль интерфейса:
const collator = new Intl.Collator('en', {
sensitivity: 'base'
});
или
const collator = new Intl.Collator('ru', {
sensitivity: 'base'
});
Важный аспект — стабильность алгоритма. Awesomplete не гарантирует
стабильную сортировку при равных значениях. Если
collator.compare(a, b) === 0, порядок элементов может
зависеть от исходного массива.
Для стабилизации добавляют вторичный ключ:
function stableSort(a, b) {
const primary = collator.compare(a.label, b.label);
if (primary !== 0) return primary;
return a.label.localeCompare(b.label);
}
Или более универсально:
function stableSort(a, b, indexMap) {
const primary = collator.compare(a.label, b.label);
return primary !== 0 ? primary : indexMap[a.id] - indexMap[b.id];
}
При тысячах элементов вызов localeCompare внутри
сортировки становится узким местом. Intl.Collator решает
проблему за счет предварительной компиляции правил сравнения.
Оптимизированный вариант:
const collator = new Intl.Collator('ru', {
sensitivity: 'base'
});
const sortFn = collator.compare.bind(collator);
И использование:
new Awesomplete(input, {
list: largeDataset,
sort: sortFn
});
Сортировка тесно связана с фильтрацией. Awesomplete сначала отбрасывает неподходящие элементы, затем сортирует оставшиеся. Это означает, что оптимизация сортировки напрямую влияет на UX автодополнения.
Комбинация фильтра и сортировки:
const collator = new Intl.Collator('ru', {
sensitivity: 'base'
});
new Awesomplete(input, {
list: data,
filter: function(text, input) {
return collator
.compare(text.slice(0, input.length), input) === 0;
},
sort: function(a, b) {
return collator.compare(a.label, b.label);
}
});
Такой подход делает поведение списка ближе к языковым ожиданиям пользователя, а не к технической сортировке строк.