Let the quick-connect palette answer for itself

Clicking outside the palette did nothing, because nothing was listening: the
wash took no pointer input at all, so the only ways out were a key and the
button that opened it. It now closes on a press whose source is the wash
itself, which is what separates outside from inside — a press on the card
bubbles through the same handler on its way to the window, and closing on
those would make the palette impossible to click into.

The caret never reached the query box either. The window focused it from the
view model's PropertyChanged, and that handler runs before the binding which
reveals the control — measured, with the same wiring, in a replica window. So
it focused a control that was still collapsed, which Avalonia treats as a
no-op and does not replay when the control is revealed, and the keyboard
stayed wherever the click that opened the palette had left it. Becoming
visible is now what triggers it, posted rather than called: a control that has
never been laid out has no visual children, and at the instant IsVisible turns
true the box still reports IsAttachedToVisualTree() == false.

Escape, Enter and the arrows move to the palette as a tunnelled handler.
Answering them only on the window was fragile in the way that matters here:
anything on the route that took a key first would silence them, and with the
focus never landing in the palette the key was being pressed at whatever the
opening click had focused — a focused Button eats Enter. The window keeps
Ctrl+K, which has to work when the palette is not showing, and forwards the
rest as the net for a press that arrives from outside the palette.

Which is also why this moved out of MainWindow rather than being fixed there.
Showing MainWindow initialises WebView2 on a thread it refuses, so nothing on
that window can be tested — the palette shipped with no test of any kind. As a
UserControl it hosts in a bare window and takes real key and pointer input,
and there are now six: press on the wash closes, press on the card does not,
Escape closes, the arrows move the selection without taking the caret out of
the box, Enter takes the highlighted host, and the palette takes the keyboard
when it appears.
This commit is contained in:
jaap-jan committed 2026-07-31 10:43:30 +02:00
1 parent 94e11f5e38
commit 312d766c30
4 files changed
+489 -64

No files matched your search

@@ -21,7 +21,13 @@
an overlay across the middle of this window would be painted underneath it and take no clicks.
-->
<Border Background="#CC0A0C0B">
<!--
The wash is named and takes a press, because clicking away from a palette is how every palette closes.
The handler answers only for presses whose source is the wash itself, which is what separates "outside"
from "inside": a press anywhere on the card below reports that control as its source and bubbles through
here on its way to the window.
-->
<Border x:Name="Backdrop" Background="#CC0A0C0B" PointerPressed="OnBackdropPressed">
<Border Width="520" VerticalAlignment="Top" Margin="0,90,0,0"
Background="{StaticResource Chrome}" BorderBrush="{StaticResource BorderMid}"
BorderThickness="1" CornerRadius="6">
@@ -39,8 +45,8 @@
</Border>
<!--
Arrow keys move the selection and Enter takes it; the window's key handler owns both, because a
ListBox that took focus would take the arrow keys away from the box being typed into.
Arrow keys move the selection and Enter takes it; the code-behind owns both, because a ListBox that
took focus would take the arrow keys away from the box being typed into.
-->
<ListBox x:Name="Results" MaxHeight="280" Focusable="False"
ItemsSource="{Binding SearchResults}"