CommandPalette
Panoramica
CommandPalette è il componente UI della command palette. Si apre e si chiude con Ctrl+K (Windows/Linux) o Cmd+K (macOS), oppure con open/close/toggle di useCommandPalette, e mostra i comandi registrati e i risultati delle fonti asincrone.
Non riceve comandi né stato come props: li legge dal singleton di useCommandPalette. Va montato una sola volta, di solito nel layout dell'applicazione. Usa <Teleport to="body">, quindi la posizione nel template è indifferente.
Anteprima live
Clicca il pulsante o premi Ctrl+K / Cmd+K. Prova «ticket» (comandi registrati), «lavaggio» (fonte asincrona con 400 ms di latenza: compare «Ricerca in corso...») e «@ro» (fonte con prefisso @, che nasconde i comandi).
Props
Tutte facoltative.
| Prop | Tipo | Default | Descrizione |
|---|---|---|---|
labels | Partial<CommandPaletteLabels> | italiano | Override parziale di testi ed etichette ARIA, per l'i18n |
loading | boolean | false | Il catalogo dei comandi è in caricamento (es. fetch del consumer): mostra «Caricamento comandi...» al posto di «nessun comando». Prevale sullo stato di caricamento delle fonti |
wrapAround | boolean | true | Le frecce ripartono dall'altro capo della lista. Con false si fermano ai bordi |
showFooter | boolean | true | Mostra il footer di aiuto alla navigazione |
maxResults | number | 100 | Righe mostrate al massimo. Oltre compare «+N altri risultati. Affina la ricerca.» |
Il tipo delle props è CommandPaletteProps.
CommandPaletteLabels
| Etichetta | Default |
|---|---|
searchPlaceholder | Cerca comando... |
searchLabel | Cerca comando (aria-label dell'input) |
dialogTitle | Tavolozza comandi (titolo del dialog per gli screen reader) |
listboxLabel | Risultati comandi |
emptyNoCommands | Nessun comando disponibile |
emptyNoResults | (query) => 'Nessun comando trovato per «query»' |
loadingLabel | Caricamento comandi... (prop loading) |
sourcesLoading | Ricerca in corso... (fonti asincrone in attesa) |
clearSearch | Cancella ricerca |
commandError | Esecuzione del comando non riuscita. Riprova. |
resultsAnnouncement | (count, query) => string: «N comandi trovati», annunciato agli screen reader. Una stringa vuota non annuncia nulla |
footerNavigate / footerSelect / footerClose | naviga / seleziona / chiudi: i verbi del footer, accanto ai tasti |
footerHint / footerHintEmpty | Testo intero del footer, con e senza risultati selezionabili. Se passati, sostituiscono il footer con i tasti |
moreResults | (hidden) => '+hidden altri risultati. Affina la ricerca.' |
<CommandPalette
:labels="{
searchPlaceholder: 'Search command...',
emptyNoResults: (query) => `No command for “${query}”`,
sourcesLoading: 'Searching...',
}"
/>
Eventi e slot
Il componente non emette eventi e non ha slot. L'esecuzione passa dall'handler del comando, gli errori da onCommandError di useCommandPalette.
Lista dei risultati
- Senza query: tutti i comandi, raggruppati sotto un header per
category(header fisso durante lo scroll) - Con una query: i comandi che corrispondono, in una lista piatta con la categoria a destra di ogni riga (troncata oltre la larghezza massima, testo intero nel
title) - Risultati delle fonti: seguono i comandi, sotto l'header della loro
category, man mano che arrivano - Ricerca in corso: finché ci sono fonti in attesa compare «Ricerca in corso...», al posto dell'empty state se non ci sono ancora risultati, altrimenti sotto la lista. «Nessun comando trovato» compare solo quando tutte le fonti hanno risposto
- Empty state: messaggio e pulsante «Cancella ricerca»
Ogni riga mostra icon, label, description, il badge shortcut e, in ricerca, la category. Un'icona con due o più parole (pi pi-home, fa fa-user) è resa come classe di un icon font, altrimenti come testo (emoji o carattere).
Se il catalogo cambia a palette aperta, la selezione resta sullo stesso comando.
Interazioni da tastiera e mouse
| Tasto | Azione |
|---|---|
Ctrl+K / Cmd+K | Apre o chiude la palette, anche con il focus in un campo di testo |
ArrowDown / ArrowUp | Selezione successiva o precedente (circolare con wrapAround) |
Enter | Esegue il comando selezionato |
Escape | Con una query la svuota, senza query chiude la palette |
Tab / Shift+Tab | Restano dentro il dialog (focus trap) |
- Il click su una riga la esegue, il click sullo sfondo chiude la palette
- La selezione segue il mouse solo quando si muove: una riga che compare sotto il cursore fermo non ruba la selezione alla tastiera
- Durante la composizione IME (cinese, giapponese, coreano) la ricerca attende il testo definitivo
All'apertura il focus va sull'input e la selezione sulla prima riga; alla chiusura torna all'elemento che lo aveva prima.
Esecuzione ed errori
Durante l'esecuzione di un comando il dialog ha aria-busy="true" e un secondo Enter viene ignorato. Se l'handler termina senza errori la palette si chiude; se lancia, resta aperta con il messaggio commandError (role="alert") e l'utente può riprovare o cercare altro. Il messaggio sparisce alla ricerca successiva.
Un comando malformato che fa fallire il rendering non fa cadere l'applicazione: l'errore viene intercettato e passato a onRenderError di useCommandPalette.
Accessibilità
- Dialog:
role="dialog",aria-modal="true", titolo per gli screen reader collegato conaria-labelledby - Sfondo: il resto della pagina diventa
inertearia-hiddenmentre la palette è aperta, e lo scroll delbodyè bloccato - Input:
role="combobox"conaria-controls,aria-autocomplete="list"earia-activedescendantsulla riga selezionata - Risultati:
role="listbox"con righerole="option"earia-selected; gli header di categoria sono presentazionali - Annunci: una regione
aria-live="polite"annuncia il numero di risultati; caricamento ed empty state sonorole="status"
Stile
Lo stile è fatto di utility Tailwind, con dark mode tramite le varianti dark:; il pacchetto non distribuisce CSS. Nel portale le classi vengono generate dalla shell (che scansiona il bundle della libreria e le ha nella safelist): la palette è della shell, non dei microfrontend. In un'applicazione standalone il Tailwind del progetto deve scansionare node_modules/@pzeta/vue-scorciatoie/dist.
Esempio base
<script setup lang="ts">
import { CommandPalette, useCommandPalette } from '@pzeta/vue-scorciatoie'
useCommandPalette([
{
id: 'apri-anagrafica',
label: 'Apri anagrafica',
category: 'Navigazione',
handler: () => router.push('/anagrafica'),
},
{
id: 'nuova-attivita',
label: 'Nuova attività',
category: 'Azioni',
keywords: ['task', 'todo'],
handler: () => apriDialogAttivita(),
},
])
</script>
<template>
<CommandPalette />
<RouterView />
</template>
Le keyword non sono visualizzate ma partecipano alla ricerca: digitando «task» si trova «Nuova attività».
Esempio con catalogo caricato dal server
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { CommandPalette, useCommandPalette } from '@pzeta/vue-scorciatoie'
const caricamento = ref(true)
const { addCommand } = useCommandPalette()
onMounted(async () => {
try {
const voci: { id: string; titolo: string; percorso: string }[] = await fetch('/api/menu').then((r) => r.json())
for (const v of voci) {
addCommand({ id: v.id, label: v.titolo, category: 'Menu', handler: () => router.push(v.percorso) })
}
} finally {
caricamento.value = false
}
})
</script>
<template>
<CommandPalette :loading="caricamento" />
</template>
Per risultati che dipendono dalla query (articoli, clienti, documenti) si usano invece le fonti asincrone.