fix(qt): Escape must free the header search field

focusSearch() force-focuses the search input and nothing in HeaderBar ever
released it: the Escape arm only fired while the cortinilla was open, and
even then it dismissed the dropdown and left focus in the field. Since the
central dispatcher refuses every binding while a text input holds focus
(AppShell.qml:133-139), the field was a keyboard dead end whose only way out
was a mouse click.

Escape now always dismisses the dropdown if open, accepts, and hands focus
to the shell root via the same isQbzShellRoot duck-walk the modals use.
Clearing focus alone would not do: a null activeFocusItem passes the gate
but leaves AppShell's Keys handler receiving nothing at all.

Reachable from Ctrl+f since the port; the Vim keymap's `/` puts it one
keystroke from the reflex that expects Escape to work, which is how it
surfaced.
This commit is contained in:
Niklas Herder committed 2026-08-28 07:57:48 +02:00
1 parent b781ed283f
commit 56be744637
1 file changed
+20 -2
+20 -2
View File
@@ -871,9 +871,27 @@ Rectangle {
}
event.accepted = true
} else if (event.key === Qt.Key_Escape) {
if (QbzSearch.cortinillaOpen) {
// Escape must ALWAYS hand the keyboard back. With the
// dropdown shut this was a no-op, and since the central
// gate refuses every binding while a text input holds
// focus (AppShell.qml:133-139), the field was a trap with
// no keyboard way out — reachable from Ctrl+f, and one
// keystroke away under the Vim keymap's `/`.
//
// Focus is handed to the shell root by the same duck-walk
// the modals use, not merely cleared: a null
// activeFocusItem passes the gate but leaves AppShell's
// Keys handler receiving nothing at all.
if (QbzSearch.cortinillaOpen)
QbzSearch.cortinillaDismiss()
event.accepted = true
event.accepted = true
var p = searchInput
while (p.parent) {
if (p.parent.isQbzShellRoot === true) {
p.parent.forceActiveFocus()
return
}
p = p.parent
}
}
}