useEscapeWhileOpen
Composable comune — usato da
useDropdownLifecycle(Select, MultiSelect, TreeSelect, AutoComplete) e da DatePicker, DateTimePicker e TimePicker. È esportato dal pacchetto per i componenti custom con un pannello teleportato.
Il problema
I campi con tendina fermano l'Escape nei propri keydown, così con la tendina aperta Escape
chiude solo la tendina e il Dialog si chiude al tasto successivo (un livello alla volta). Ma
quei listener ricevono l'evento solo se il focus è dentro il campo o il pannello: dopo il clic
su un elemento non focalizzabile (un'opzione, lo sfondo del pannello, un pulsante che poi
sparisce) il focus finisce sul body, l'Escape parte da lì e arriva dritto al listener del
Dialog su document, chiudendo insieme tendina e dialog e perdendo quanto scritto.
Firma TypeScript
export function useEscapeWhileOpen(options: UseEscapeWhileOpenOptions): void
export interface UseEscapeWhileOpenOptions {
/** Stato del pannello: il listener e' registrato solo a pannello aperto. */
isOpen: WatchSource<boolean>
/** Campo e pannello: un Escape nato qui dentro lo gestisce gia' il componente. */
inside: () => (Node | null | undefined)[]
/** Chiude il pannello; focusLost e' true se il focus era sul body. */
close: (focusLost: boolean) => void
}
Parametri
| Parametro | Tipo | Descrizione |
|---|---|---|
isOpen | WatchSource<boolean> | Stato del pannello (ref, computed o getter). Il listener esiste solo a pannello aperto |
inside | () => (Node | null | undefined)[] | Elementi del componente — di norma campo e pannello teleportato. Gli Escape nati qui dentro restano ai gestori del componente; i valori nulli sono ignorati |
close | (focusLost: boolean) => void | Chiude il pannello. Con focusLost a true il focus era sul body, e il componente può riportarlo sul campo senza toglierlo a un altro elemento |
Comportamento
- A pannello aperto registra un listener
keydownsudocumentin fase di cattura: parte prima dei listener in bubbling di Dialog e Drawer sullo stesso nodo, e lostopPropagation()li salta. - Interviene solo per l'Escape nato fuori dagli elementi di
inside; gli altri tasti e gli Escape interni passano senza modifiche. - Rimuove il listener alla chiusura del pannello e allo smontaggio del componente.
useDropdownLifecyclelo usa già: un componente costruito su quel composable non deve aggiungerlo.
Esempio
import { nextTick, ref } from 'vue'
import { useEscapeWhileOpen } from '@pzeta/vue-components'
const isOpen = ref(false)
const campoRef = ref<HTMLElement | null>(null)
const pannelloRef = ref<HTMLElement | null>(null) // pannello teleportato nel body
const inputRef = ref<HTMLInputElement | null>(null)
function chiudi() {
isOpen.value = false
}
useEscapeWhileOpen({
isOpen,
inside: () => [campoRef.value, pannelloRef.value],
close: (focusLost) => {
chiudi()
if (focusLost) nextTick(() => inputRef.value?.focus())
},
})
Per gli Escape interni il componente resta responsabile: a pannello aperto chiama
event.stopPropagation() sull'Escape che lo chiude (closeDropdownOnEscape di
useDropdownLifecycle lo fa già).