hermes-agent/apps/desktop/src/lib/input-modality.ts
Brooklyn Nicholson 914c3f46c6 fix(desktop): stop the model-pill tooltip popping open after a mouse pick
Picking a model closed the menu and then flashed the pill's tooltip over
the fresh selection. The existing focus guard gated on `:focus-visible`
alone, which does not separate a mouse pick from a Tab: the dropdown
autofocuses its search field and keyboard-navigates its rows, so Chromium
is already in keyboard modality when the menu restores focus to the
trigger, and the guard never fired.

Track the device behind the last real interaction and use it to qualify
`:focus-visible`. A mouse pick reports `pointer` and stays silent; Tab
focus still opens the tip.
2026-07-28 02:27:11 -05:00

26 lines
1.2 KiB
TypeScript

/** Which input device drove the most recent interaction.
*
* Chromium's `:focus-visible` is not enough to tell a mouse pick from a Tab.
* Radix menus autofocus their content on open and keyboard-navigate their
* items, so by the time a mouse click closes the menu and restores focus to
* the trigger, Chromium has decided the page is in keyboard modality and
* matches `:focus-visible` on that restore (verified in Chrome — the model
* pill's tooltip popped open after every mouse pick). Track the device
* ourselves and use it to qualify `:focus-visible`.
*/
export type InputModality = 'keyboard' | 'pointer'
let modality: InputModality = 'keyboard'
/** Capture-phase so a `stopPropagation` deeper in the tree can't blind us. */
if (typeof document !== 'undefined') {
document.addEventListener('pointerdown', () => (modality = 'pointer'), { capture: true, passive: true })
document.addEventListener('keydown', () => (modality = 'keyboard'), { capture: true, passive: true })
}
/** The device behind the last pointerdown/keydown. Defaults to `keyboard` so a
* surface that has seen no input yet keeps the accessible behavior. */
export function lastInputModality(): InputModality {
return modality
}