open atlas
← Все проекты

frontend · intermediate · 3d

Палитра команд

Палитра команд ⌘K с нечётким ранжированием, асинхронными источниками действий и полным клавиатурным управлением (стрелки, Enter, Escape, области видимости) — интерактивный слой, который нужен каждому профессиональному инструменту.

Палитры команд обманчиво малы, но затрагивают удивительно широкий спектр сложных задач: нечёткое ранжирование, ощущающееся правильным, асинхронные источники без гонок, вложенное навигационное состояние и доступное клавиатурное управление. Собери её один раз правильно — и больше никогда не потянешься за библиотекой, заодно поняв, почему каждая библиотека что-то из этого делает неправильно.

Результат

Палитра, открывающаяся по ⌘K, нечётко фильтрующая зарегистрированные команды при вводе, поддерживающая вложенные области (например, вход в подменю 'Theme >') и полностью управляемая только с клавиатуры.

Этапы

0/3 · 0%
  1. 01Реестр + управление с клавиатуры

    Собери реестр команд с синхронными командами, открытием/закрытием через ⌘K и обработкой клавиш стрелок, Enter и Escape.

    Критерии готовности
    • ⌘K открывает/закрывает палитру; стрелки двигают активный пункт, Enter запускает, Escape закрывает — всё без мыши.
    • Фокус заперт внутри открытой палитры и возвращается на триггер при закрытии.
  2. 02Нечёткое ранжирование

    Добавь нечёткую фильтрацию со scoring-функцией, ставящей точные префиксные совпадения выше рассеянных, с подсветкой совпавших символов.

    Критерии готовности
    • При вводе точные префиксные совпадения ранжируются выше разбросанных подпоследовательностей, а совпавшие символы подсвечиваются.
    • Пустой запрос показывает все команды; нет совпадений — явное пустое состояние.
  3. 03Асинхронные источники с дебаунсом

    Поддержи асинхронные источники действий (например, search API) с дебаунсом, индикатором загрузки и корректной отменой устаревших запросов.

    Критерии готовности
    • Асинхронный источник дебаунсится, показывает загрузку, а устаревший in-flight запрос отменяется при смене ввода.
    • Результаты отменённого запроса никогда не перезаписывают результаты последнего запроса.

Стартер

  • README.md
  • src/palette.ts
  • test/palette.test.ts
Скачать стартер (.zip)

Распакуй, реализуй заглушки, затем гоняй тесты, пока не позеленеют: bun test

Рубрика

Джуниор Миддл Сеньор
Качество совпадения Фильтрует элементы по вхождению подстроки; все совпадающие элементы выводятся без учёта позиции. Реализует поиск по подпоследовательности (не подстроке) и ставит префиксные / смежные совпадения выше рассеянных, чтобы нужный элемент оказывался первым. Функция оценки чистая и тестируется отдельно; ничьи разрешаются детерминированно; функция корректно обрабатывает границы Unicode, а её временная сложность указана и обоснована (O(n·m) на элемент, итого O(N·m log N)).
Корректность клавиатурной модели Стрелки перемещают выделение, Enter запускает выделенный элемент; перенос отсутствует или ломается на границах. Выделение правильно оборачивается в обоих направлениях, включая список из одного элемента или пустой; reduce — чистая функция без побочных эффектов. Редьюсер — единственный источник истины для всего клавиатурного состояния; управление фокусом (ловушка при открытии, восстановление при закрытии) вынесено за пределы редьюсера, чтобы чистая логика тестировалась в Node без DOM; граничные случаи (пустой список, быстрые нажатия) покрыты юнит-тестами.
Производительность под нагрузкой Перестраивает весь список при каждом нажатии клавиши синхронно; незаметно при коротких списках (< 50 элементов). Асинхронные источники дебаунсятся; функция ранжирования мемоизируется или пропускается при неизменном запросе; устаревшие in-flight запросы отменяются. Список из 10 000+ команд отображается без лагов: либо виртуализирован (в DOM только видимые строки), либо проиндексирован заранее (trie / инвертированный индекс строится один раз при регистрации, чтобы работа на каждое нажатие была сублинейной). Выбор обоснован измеренным компромиссом между памятью и CPU.
Эталонный разбор (спойлер)

Подпоследовательность vs подстрока: нечёткое сопоставление проверяет, что каждый символ запроса встречается в элементе по порядку, но не обязательно подряд — 'fc' совпадает с 'fuzzyCheck', потому что 'f' стоит перед 'c'. Расстояние Левенштейна — другая мера (минимум вставок/удалений/замен), которая одинаково штрафует пропуски; поиск по подпоследовательности дешевле (O(n) на кандидата) и ощущается естественнее для поиска команд, поскольку вознаграждает совпадение инициалов слов в camelCase.

Ранжирование как чистая функция: логика оценки должна принимать (items, query) и возвращать отсортированный массив без побочных эффектов — без мутаций состояния, без обращений к DOM, без асинхронной работы. Эта граница делает её юнит-тестируемой в Node, упрощает мемоизацию (те же входы → тот же выход) и позволяет слою отрисовки оставаться тонким представлением, которое только отображает ранжированные результаты в узлы DOM.

Дебаунс и отмена для асинхронных источников: дебаунс откладывает вызов асинхронного источника до паузы пользователя в наборе текста (обычно 150–250 мс), избегая сетевого round trip на каждое нажатие. AbortController позволяет отменить in-flight fetch, когда следующее нажатие происходит до получения предыдущего результата, предотвращая перезапись быстрого ответа на поздний запрос медленным ответом на ранний.

Виртуализация большого списка команд: рендеринг тысяч узлов DOM сразу вызывает перерасчёты макета. Виртуализатор (например, отрисовка только 8–12 видимых строк плюс небольшой буфер overscan) сохраняет DOM постоянного размера вне зависимости от длины списка. Компромисс — сложность отслеживания позиции прокрутки; для списков менее ~200 элементов накладные расходы виртуализации превышают выгоду, поэтому измеряй перед добавлением.

Сделай по-сеньорски

  • Добавь области видимости: команда может открывать новый контекст (например, 'Switch project >') со своим отфильтрованным списком, навигацией-хлебными крошками и клавишей Backspace для выхода из области.
  • Сделай палитру доступной: правильные ARIA-роли combobox + listbox, объявления live region о количестве результатов и focus trap, восстанавливающий фокус при закрытии.

Навыки

fuzzy search / scoring (fzf-style)async data sources with debouncefocus trap + ARIA comboboxcommand registry pattern

Рекомендуемый стек

preacttypescript