Skip to content

Справочник

Тип Command

ts
interface Command<T = unknown> {
  id: string // уникальный идентификатор
  label: string // отображаемый текст, ищется движком нечёткого поиска
  description?: string // подзаголовок под label
  icon?: Component | string // Vue-компонент или эмодзи / строка
  keywords?: string[] // дополнительные термины для поиска
  aliases?: string[] // альтернативные label (тот же счёт, что и совпадение по label)
  shortcut?: string[] // подсказка только для отображения: ['$mod', 'k']
  disabled?: boolean // постоянно недоступна
  enabled?: () => boolean // динамическое отключение — вычисляется на каждом рендере
  disabledReason?: string // тултип, показываемый когда команда отключена
  badge?: string | { text: string; color?: string } // небольшая метка (например, "New", "Pro")
  confirm?: string // подсказка перед выполнением
  perform: () => void | Promise<void> // действие; может быть асинхронным
  subCommands?: Command[] // открывает вложенную палитру при выборе
  page?: CommandPage // открывает страницу со своим вводом/асинхронным поиском
  actions?: CommandAction[] // дополнительные действия, открываются по Tab
  info?: string // текст/HTML, показываемый в панели предпросмотра (v-html)
  data?: T // типобезопасный payload (см. «Типизированные данные команды»)
}

interface CommandAction {
  id: string
  label: string
  icon?: Component | string
  shortcut?: string[] // подсказка только для отображения
  perform: () => void | Promise<void>
}

interface CommandPage {
  placeholder?: string // плейсхолдер ввода на странице
  items?: Command[] // статичные элементы (пустой запрос)
  onSearch?: (query: string) => Command[] | Promise<Command[]> // результаты по запросу (с debounce)
}

Поле icon

Принимает строку-эмодзи, обычную текстовую строку или любой Vue-компонент:

ts
import MyIcon from './MyIcon.vue'

{
  icon: '🏠'
} // строка-эмодзи
{
  icon: '⌘'
} // строка-символ
{
  icon: MyIcon
} // Vue-компонент — рендерится как <MyIcon />

Типизированные данные команды

Прикрепите произвольный типобезопасный payload к командам через дженерик Command<T> и его поле data. useRegisterCommands<T> / useRegisterGroup<T>, fuzzySearch<T>, SearchResult<T> и SearchFn<T> — все пробрасывают тип дальше, так что вы получаете полный вывод типов (и ошибки при несовпадении). По умолчанию используется unknown, поэтому существующий нетипизированный код не затрагивается.

ts
interface UserData {
  id: number
  email: string
}

useRegisterCommands<UserData>([
  {
    id: 'user-ada',
    label: 'Ada Lovelace',
    data: { id: 1, email: 'ada@example.com' }, // проверяется по UserData
    perform: () => {},
  },
])

// Автономный поиск сохраняет тип:
const results = fuzzySearch<UserData>('ada', commands)
results[0].command.data?.email // string | undefined
ts
// @ts-expect-error — data должен соответствовать UserData
const bad: Command<UserData> = { id: 'x', label: 'X', data: { wrong: true }, perform: () => {} }

Внутри слотов #item / #preview command типизирован как Command (data: unknown), поскольку палитра хранит команды смешанных типов — сужайте тип через приведение или type guard, когда там нужен payload.

Типы TypeScript

Все публичные типы экспортируются из корня пакета:

ts
import type {
  Command,
  CommandGroupType, // определение группы — НЕ компонент CommandGroup
  CommandAction, // дополнительное действие команды
  CommandPage, // страница, открываемая командой (плейсхолдер + асинхронный onSearch)
  SearchResult, // { command, score, matches, groupId?, parents?, matchedField? }
  SearchFn, // сигнатура кастомной стратегии поиска
  PaletteMode, // активируемая префиксом область
  CommandUsage, // статистика frecency { count, lastUsed }
  PaletteOptions,
  PaletteLabels, // настраиваемые строки UI (i18n)
  PaletteContext,
  PaletteState,
  CommandStore,
  KeyboardManager,
} from '@macrulez/vue-command-palette'

Примечание: именованный экспорт CommandGroup — это Vue-компонент. Интерфейс определения группы экспортируется как CommandGroupType, чтобы избежать конфликта.

SearchResult

ts
interface SearchResult {
  command: Command
  score: number
  matches: Array<[start: number, end: number]>
  groupId?: string
  parents?: Command[] // цепочка предков, когда результат — вложенная под-команда
  matchedField?: 'label' | 'description' | 'keyword' | 'alias' // какое поле выиграло счёт
  matchedText?: string // текст совпавшего ключевого слова/алиаса
}

PaletteContext

Полный инжектируемый контекст, доступный в кастомных composables через inject(PALETTE_INJECT_KEY):

ts
interface PaletteContext {
  store: CommandStore
  keyboard: KeyboardManager
  isOpen: Ref<boolean>
  query: Ref<string>
  activeIndex: Ref<number>
  history: Ref<HistoryEntry[]>
  recentIds: Ref<string[]>
  loadingCommandId: Ref<string | null>
  results: ComputedRef<SearchResult[]>
  persistRecent: boolean
  maxRecent: number
  maxRecentPerGroup: number
  localStorageKey: string
  onOpen?: () => void
  onClose?: () => void
  onError?: (err: unknown, command: Command) => void
}

Доступность

ФункцияРеализация
role="dialog" + aria-modal="true"Применяется к элементу диалога палитры
role="combobox"Применяется к <input> поиска
aria-expanded="true"Устанавливается на input, пока палитра открыта
aria-controlsInput указывает на список результатов с role="listbox"
aria-activedescendantОбновляется при смене активного по клавиатуре пункта
role="listbox"Применяется к контейнеру списка результатов
role="option"Применяется к каждому CommandItem
aria-selectedУстанавливается в true для текущего активного пункта
aria-disabledУстанавливается, когда disabled: true или enabled() возвращает false
aria-live="polite"Хлебные крошки — скринридеры объявляют навигацию по под-палитре; визуально скрытая область также объявляет количество результатов (labels.resultsCount)
Focus trapTab перехватывается, чтобы удерживать фокус внутри диалога
Блокировка скроллаdocument.body.style.overflow устанавливается в hidden, пока диалог открыт
Уменьшенное движение@media (prefers-reduced-motion: reduce) отключает fade-переход

Совместимость с SSR

Все браузерные API защищены перед использованием:

ts
// KeyboardManager — пропускает addEventListener на сервере
if (typeof document === 'undefined') return

// Недавние команды — пропускает localStorage на сервере
if (typeof localStorage === 'undefined') return

// CommandItem — определение платформы для метки ⌘ или Ctrl
typeof navigator !== 'undefined' && navigator.platform.includes('Mac')

VirtualList (используется для наборов результатов > 50 элементов) рендерит пустой плейсхолдер на сервере и гидрируется на клиенте. Весь контент слотов и регистрация команд полностью SSR-безопасны.

Размер бандла

Точка входаPeer-зависимостиЗаметки
@macrulez/vue-command-palettevue ^3.3Компоненты, composables, движок нечёткого поиска, менеджер клавиатуры
@macrulez/vue-command-palette/style.cssСтандартные стили; ~3 КБ
@macrulez/vue-command-palette/testingvue ^3.3createPaletteContext + PaletteProvider; только для dev/тестов
@macrulez/vue-command-palette/nuxtnuxt ^3, vue ^3.3Авто-плагин для Nuxt

Поставляется как tree-shakeable ESM (dist/@macrulez/vue-command-palette.js) + CJS (dist/@macrulez/vue-command-palette.cjs). Базовый бандл без стилей — ~11 КБ gzip; стилевой файл — ~2 КБ gzip.

Лицензия

MIT