В компоненте Tom Select управление кареткой (caret position) является
ключевым механизмом, определяющим удобство ввода, редактирования и
навигации внутри инпута. Каретка в данном контексте — это позиция
текстового курсора внутри внутреннего input-элемента
(control_input), который Tom Select использует для ввода
поискового запроса или создания новых значений.
В отличие от обычного HTML input, где каретка управляется напрямую браузером, здесь добавляется слой логики: вставка тегов, фильтрация опций, управление выбранными элементами и динамическое изменение значения поля. Это делает позиционирование каретки зависимым от состояния компонента.
Tom Select использует скрытую структуру ввода:
control — контейнер всего компонентаcontrol_input — реальный текстовый inputitems — выбранные элементы (теги)dropdown — список опцийКаретка существует только внутри control_input, но её
поведение зависит от:
multiple или singleКаждое изменение списка выбранных элементов может привести к пересозданию или перерасчёту input, что делает контроль позиции каретки нетривиальной задачей.
Основной публичный метод управления положением курсора:
select.setCaret(position);
Где position — индекс символа внутри строки input.
const select = new TomSelect("#select");
select.setTextboxValue("hello world");
select.setCaret(11);
После выполнения каретка окажется в конце строки.
Tom Select не всегда хранит явное состояние каретки, поэтому фактическое положение определяется через DOM-элемент:
const input = select.control_input;
const position = input.selectionStart;
Для диапазона выделения:
const start = input.selectionStart;
const end = input.selectionEnd;
Это важно при обработке пользовательского ввода, особенно при фильтрации или автодополнении.
При вызове:
select.setTextboxValue("abc");
происходит обновление DOM-значения, но каретка может:
Чтобы зафиксировать позицию, используется комбинация:
const pos = select.control_input.selectionStart;
select.setTextboxValue("abcdef");
select.setCaret(pos);
Однако это работает корректно только если новая строка длиннее или равна старой.
В режиме multiple добавление элемента:
select.addItem("value");
приводит к:
Типичная проблема: после добавления тега каретка уходит в начало строки.
Решение — восстановление позиции:
const pos = select.control_input.selectionStart;
select.addItem("value");
select.setCaret(pos);
При удалении символов или элементов поведение каретки становится зависимым от контекста:
Пример обработки:
select.on("keydown", (e) => {
if (e.key === "Backspace") {
const input = select.control_input;
if (input.selectionStart === 0 && !input.value) {
const lastItem = select.items[select.items.length - 1];
select.removeItem(lastItem);
}
}
});
Некоторые действия приводят к полной пересборке input:
refreshOptions()clearOptions()itemsrenderВ таких случаях DOM input может быть заменён, и каретка сбрасывается.
Типичный паттерн восстановления:
function preserveCaret(select, fn) {
const input = select.control_input;
const pos = input.selectionStart;
fn();
requestAnimationFrame(() => {
select.setCaret(pos);
});
}
При вводе на языках с IME (например, китайский, японский, корейский) каретка может вести себя нестабильно из-за промежуточного состояния композиции.
Ключевые события:
compositionstartcompositionupdatecompositionendПример защиты:
let composing = false;
select.control_input.addEventListener("compositionstart", () => {
composing = true;
});
select.control_input.addEventListener("compositionend", () => {
composing = false;
});
В таком режиме любые операции с setCaret должны
откладываться до завершения композиции.
Фильтрация опций может вызывать обновление списка и перерисовку dropdown.
Если пользователь вводит текст:
select.on("type", (query) => {
const pos = select.control_input.selectionStart;
select.refreshOptions(false);
select.setCaret(pos);
});
Однако при быстром вводе возможна десинхронизация, поэтому часто применяется debounce:
let t;
select.on("type", () => {
clearTimeout(t);
t = setTimeout(() => {
const pos = select.control_input.selectionStart;
select.setCaret(pos);
}, 50);
});
В multiple-режиме важно учитывать ширину контейнера: при переполнении
input переносится на новую строку, что визуально изменяет позицию
каретки без изменения selectionStart.
Для принудительного управления используется связка методов:
select.focus();
select.setTextboxValue("text");
select.setCaret(4);
Иногда требуется гарантировать фокус:
select.control_input.focus();
select.setCaret(select.control_input.value.length);
Каретка тесно связана с обработкой клавиатуры:
Пример кастомной логики:
select.on("keydown", (e) => {
const input = select.control_input;
if (e.key === "ArrowLeft" && input.selectionStart === 0) {
select.setCaret(0);
}
if (e.key === "End") {
select.setCaret(input.value.length);
}
});
Хотя каретка управляется логически через selectionStart,
визуально она зависит от:
Особенно критично в multi-line режимах: логическая позиция может не совпадать с визуальной.
setCaret до появления input в DOMselectionStart === nullДля стабильного поведения каретки используется комбинированный подход:
selectionStart до измененийrequestAnimationFramefunction safeUpdate(select, updateFn) {
const input = select.control_input;
const pos = input.selectionStart || 0;
updateFn();
requestAnimationFrame(() => {
if (!document.activeElement === input) return;
select.setCaret(pos);
});
}