diff --git a/src/ClaudeDo.App/App.axaml.cs b/src/ClaudeDo.App/App.axaml.cs index cd99389..aaed0c0 100644 --- a/src/ClaudeDo.App/App.axaml.cs +++ b/src/ClaudeDo.App/App.axaml.cs @@ -21,6 +21,8 @@ public partial class App : Application { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { + FocusClearing.Install(); + desktop.MainWindow = new MainWindow { DataContext = Services.GetRequiredService(), diff --git a/src/ClaudeDo.Ui/Services/FocusClearing.cs b/src/ClaudeDo.Ui/Services/FocusClearing.cs new file mode 100644 index 0000000..3edef8f --- /dev/null +++ b/src/ClaudeDo.Ui/Services/FocusClearing.cs @@ -0,0 +1,34 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.VisualTree; + +namespace ClaudeDo.Ui.Services; + +/// +/// Clears focus from a TextBox when the user clicks outside of any text box, so input +/// fields behave like the user expects. Registered once for every window in the app. +/// +public static class FocusClearing +{ + public static void Install() + { + InputElement.PointerPressedEvent.AddClassHandler( + OnPointerPressed, RoutingStrategies.Tunnel, handledEventsToo: true); + } + + private static void OnPointerPressed(TopLevel topLevel, PointerPressedEventArgs e) + { + if (topLevel.FocusManager is not { } focusManager) + return; + + if (focusManager.GetFocusedElement() is not TextBox) + return; + + if (e.Source is Visual v && v.FindAncestorOfType(includeSelf: true) is not null) + return; + + focusManager.Focus(null); + } +}