feat(ui): clear focus from textboxes on Escape in the main window
Mirrors the existing click-outside behavior. Scoped to MainWindow only (not a generic TopLevel handler) so modal Escape-to-close bindings and Mission Control's ConPTY tiles are unaffected.
This commit is contained in:
@@ -66,6 +66,7 @@ Modals use `TaskCompletionSource` results behind the reusable `ModalShell` contr
|
|||||||
- "Run Now" CanExecute re-evaluates when worker connection state changes
|
- "Run Now" CanExecute re-evaluates when worker connection state changes
|
||||||
- Icon gotcha: `PathIcon` fills geometry. Line-art/stroke icons must be defined as filled geometry or rendered as a stroked `Path` (e.g. `Icon.PlanDay` via the `Path.plan-icon` style); a pure stroke path used with `PathIcon` is invisible.
|
- Icon gotcha: `PathIcon` fills geometry. Line-art/stroke icons must be defined as filled geometry or rendered as a stroked `Path` (e.g. `Icon.PlanDay` via the `Path.plan-icon` style); a pure stroke path used with `PathIcon` is invisible.
|
||||||
- Window key bindings live on `MainWindow`: `Ctrl+K` focuses search, `Ctrl+N` the add-task box. Do **not** bind bare punctuation gestures — `OemQuestion` used to hold search focus and silently swallowed `#` app-wide on a German layout.
|
- Window key bindings live on `MainWindow`: `Ctrl+K` focuses search, `Ctrl+N` the add-task box. Do **not** bind bare punctuation gestures — `OemQuestion` used to hold search focus and silently swallowed `#` app-wide on a German layout.
|
||||||
|
- `FocusClearing` also clears focus from a TextBox on Escape, mirroring its click-outside behavior — but the KeyDown handler is scoped to `MainWindow` specifically (`AddClassHandler<MainWindow>`, not `<TopLevel>`). Modal windows (`AboutModalView` etc.) each bind their own `Window.KeyBindings` Escape → close; since modals are separate `Window` instances, this handler never runs there, so Escape still closes them unchanged. Mission Control's ConPTY tiles (`InteractiveTerminalView`) live in `MissionControlWindow`, also unaffected — Escape always reaches the PTY there.
|
||||||
- `Ellipse.spinner` (IslandStyles) is the shared indeterminate spinner: used for a starting ConPTY pane (`InteractiveTerminalViewModel.IsStarting`) and in place of the refine button while `TaskRowViewModel.IsRefining`.
|
- `Ellipse.spinner` (IslandStyles) is the shared indeterminate spinner: used for a starting ConPTY pane (`InteractiveTerminalViewModel.IsStarting`) and in place of the refine button while `TaskRowViewModel.IsRefining`.
|
||||||
- `ConPtyPaneViewModel` resolves its own launch spec (ctor takes a descriptor **factory**; the host wires handlers and then calls `Start()`), so the Mission Control tile appears immediately with its spinner while the worker is still preparing the worktree. A failed launch keeps the tile with its inline error banner instead of never appearing.
|
- `ConPtyPaneViewModel` resolves its own launch spec (ctor takes a descriptor **factory**; the host wires handlers and then calls `Start()`), so the Mission Control tile appears immediately with its spinner while the worker is still preparing the worktree. A failed launch keeps the tile with its inline error banner instead of never appearing.
|
||||||
- `SessionTerminalView` is the reusable log terminal (StyledProperties `Entries`, `Label`, `IsRunning`, `IsDone`, `IsFailed`) used for both the task `Log` and the prep `PrepLog`.
|
- `SessionTerminalView` is the reusable log terminal (StyledProperties `Entries`, `Label`, `IsRunning`, `IsDone`, `IsFailed`) used for both the task `Log` and the prep `PrepLog`.
|
||||||
|
|||||||
@@ -3,12 +3,16 @@ using Avalonia.Controls;
|
|||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.VisualTree;
|
using Avalonia.VisualTree;
|
||||||
|
using ClaudeDo.Ui.Views;
|
||||||
|
|
||||||
namespace ClaudeDo.Ui.Services;
|
namespace ClaudeDo.Ui.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clears focus from a TextBox when the user clicks outside of any text box, so input
|
/// 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.
|
/// fields behave like the user expects. Registered once for every window in the app.
|
||||||
|
/// Escape does the same, but only in the main window: modal windows bind Escape to closing
|
||||||
|
/// themselves, and Mission Control's ConPTY tiles need Escape to reach the terminal, so this
|
||||||
|
/// intentionally does not use a generic TopLevel handler.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class FocusClearing
|
public static class FocusClearing
|
||||||
{
|
{
|
||||||
@@ -16,6 +20,9 @@ public static class FocusClearing
|
|||||||
{
|
{
|
||||||
InputElement.PointerPressedEvent.AddClassHandler<TopLevel>(
|
InputElement.PointerPressedEvent.AddClassHandler<TopLevel>(
|
||||||
OnPointerPressed, RoutingStrategies.Tunnel, handledEventsToo: true);
|
OnPointerPressed, RoutingStrategies.Tunnel, handledEventsToo: true);
|
||||||
|
|
||||||
|
InputElement.KeyDownEvent.AddClassHandler<MainWindow>(
|
||||||
|
OnKeyDown, RoutingStrategies.Tunnel);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void OnPointerPressed(TopLevel topLevel, PointerPressedEventArgs e)
|
private static void OnPointerPressed(TopLevel topLevel, PointerPressedEventArgs e)
|
||||||
@@ -31,4 +38,19 @@ public static class FocusClearing
|
|||||||
|
|
||||||
focusManager.Focus(null);
|
focusManager.Focus(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void OnKeyDown(MainWindow window, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Key != Key.Escape)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (window.FocusManager is not { } focusManager)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (focusManager.GetFocusedElement() is not TextBox)
|
||||||
|
return;
|
||||||
|
|
||||||
|
focusManager.Focus(null);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user