Getting Started

Registrazione scorciatoie

Pattern di registrazione scorciatoie globali e contestuali, modificatori cross-platform ed esempi pratici.

Panoramica

Le scorciatoie si registrano tramite il composable useShortcuts, che mantiene un registro globale singleton. Una scorciatoia è identificata da id univoco e può combinare un tasto principale con i modificatori ctrl, alt, shift, meta.

Definizione di una scorciatoia

import type { Shortcut } from '@pzeta/vue-scorciatoie'

const shortcut: Shortcut = {
  id: 'salva-record',
  key: 's',
  ctrl: true,
  description: 'Salva il record corrente',
  handler: () => salvaRecord(),
  enabled: true,
  allowInInput: false,
}
CampoTipoDefaultDescrizione
idstringIdentificatore univoco. Una seconda registrazione con lo stesso id viene ignorata
keystringTasto principale (case-insensitive, es. 's', 'Enter', 'ArrowUp')
ctrlbooleanfalseRichiede Ctrl (Windows/Linux) o Cmd (macOS)
altbooleanfalseRichiede Alt (Windows/Linux) o Ctrl (macOS)
shiftbooleanfalseRichiede Shift
metabooleanfalseRichiede tasto Meta esplicito (Windows/Linux). Su macOS il matching meta è sostituito dalla logica BR-18
descriptionstringDescrizione testuale per cheat-sheet o tooltip
handler() => void | Promise<void>Funzione invocata quando la scorciatoia è premuta. Errori asincroni vengono loggati con console.error
enabledbooleantrueSe false, la scorciatoia è ignorata senza essere rimossa
allowInInputbooleanfalseSe false, la scorciatoia non scatta quando il focus è su <input>, <textarea>, <select> o elementi contentEditable

Scorciatoie globali

Registrate in un componente di alto livello (es. App.vue o un layout) restano attive per l'intera vita dell'applicazione finché lo scope non viene disposto.

<script setup lang="ts">
import { useShortcuts } from '@pzeta/vue-scorciatoie'

useShortcuts([
  {
    id: 'apri-palette',
    key: 'k',
    ctrl: true,
    description: 'Apri command palette',
    handler: () => paletteStore.open(),
  },
  {
    id: 'cerca-globale',
    key: '/',
    description: 'Focus barra di ricerca globale',
    handler: () => document.getElementById('search')?.focus(),
    allowInInput: false,
  },
])
</script>

Scorciatoie contestuali

Registrate in un componente specifico vengono rimosse automaticamente quando il componente è smontato, grazie a onScopeDispose interno.

<script setup lang="ts">
// Solo nella vista dettaglio anagrafica
import { useShortcuts } from '@pzeta/vue-scorciatoie'

useShortcuts([
  {
    id: 'anagrafica-salva',
    key: 's',
    ctrl: true,
    description: 'Salva anagrafica',
    handler: handleSave,
    allowInInput: true, // Anche se il focus è su un campo del form
  },
  {
    id: 'anagrafica-elimina',
    key: 'Delete',
    shift: true,
    description: 'Elimina anagrafica',
    handler: handleDelete,
  },
])
</script>

Cross-platform: BR-17 e BR-18

Le scorciatoie si definiscono sempre con la semantica Windows/Linux. Su macOS il matching adatta automaticamente:

DefinizioneWindows/LinuxmacOS
alt: trueAlt + keyCtrl + key (BR-17)
ctrl: trueCtrl + keyCmd + key (BR-18)

Su macOS il tasto Option fisico non deve essere premuto per evitare conflitti con i caratteri speciali Option+key.

useShortcuts([
  {
    id: 'nuova-anagrafica',
    key: 'n',
    alt: true, // Alt+N su Windows/Linux, Ctrl+N su macOS
    description: 'Nuova anagrafica',
    handler: () => apriDialogNuova(),
  },
])

Per visualizzare correttamente il modificatore nell'interfaccia, usare il composable useOS e le sue label primaryModifierLabel / paletteModifierLabel.

Configurazione globale

Il secondo parametro di useShortcuts accetta una ShortcutConfig che viene fusa nello state globale:

useShortcuts(scorciatoie, {
  preventDefault: true,    // Default true
  stopPropagation: true,   // Default true
  enabled: ref(isEnabled), // Accetta boolean o Ref<boolean>
})

La configurazione è applicata a tutte le scorciatoie registrate. enabled accetta un Ref<boolean> per disabilitare temporaneamente l'intero sistema (es. quando una modale ha il focus).

Aggiungere e rimuovere dinamicamente

useShortcuts espone API imperativa per registrazioni dinamiche:

const { addShortcut, removeShortcut, shortcuts } = useShortcuts()

addShortcut({
  id: 'temporanea',
  key: 'F2',
  description: 'Rinomina',
  handler: rinomina,
})

// In un secondo momento
removeShortcut('temporanea')

// Lista corrente (readonly)
console.log(shortcuts.value.length)

Priorità ed ordine di matching

Quando un evento keydown corrisponde a più scorciatoie, viene invocato l'handler della prima trovata nell'array (ordine di registrazione). Il loop si interrompe al primo match: usa l'ordine di registrazione come priorità implicita.

Comportamento in input

Per default le scorciatoie sono ignorate quando il focus è su un elemento editabile (input, textarea, select, [contenteditable]). Imposta allowInInput: true per permettere il match anche durante la digitazione (es. salvataggio con Ctrl+S mentre si compila un form).

Esempi pratici

Salva con Ctrl+S, anche durante la digitazione

useShortcuts([
  {
    id: 'salva',
    key: 's',
    ctrl: true,
    allowInInput: true,
    description: 'Salva',
    handler: salvaForm,
  },
])

Annulla con Esc fuori dagli input

useShortcuts([
  {
    id: 'annulla',
    key: 'Escape',
    description: 'Chiudi pannello',
    handler: () => panelStore.close(),
  },
])

Disabilitazione condizionale

const { addShortcut, removeShortcut } = useShortcuts()

watch(modalAperta, (aperta) => {
  if (aperta) {
    addShortcut({
      id: 'modal-chiudi',
      key: 'Escape',
      description: 'Chiudi modal',
      handler: chiudiModal,
    })
  } else {
    removeShortcut('modal-chiudi')
  }
})