Repository: AvaloniaUI/Avalonia

Issue:
Button should check `RoutedEventArgs.Source` before handling keys
### Describe the bug

Reported in https://github.com/amwx/FluentAvalonia/issues/591. 

Placing a text control into an Expander header does not allow spaces. The cause is the `Button` that is found in the Expander's header (which is part of the template for my SettingsExpander control in the referenced issue) is consuming this event and the TextBox never sees it.

I looked at WPF source, they do a check in `ButtonBase` to see if the event source is the button and only handle it in that case - Avalonia should do the same check.

https://github.com/dotnet/wpf/blob/0628a7722ccdc47ebe66beb5eb8cee756d05f7d9/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/Primitives/ButtonBase.cs#L612-L647

### To Reproduce

Add a TextBox to an Expander and try to type text with spaces. Only the text will be entered, the space key will open/close the expander.

### Expected behavior

_No response_

### Avalonia version

11.1-rc1

### OS

Windows

### Additional context

_No response_


Hints:

Patch:
diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs
index dc836a08716..614847ee4e8 100644
--- a/src/Avalonia.Controls/Button.cs
+++ b/src/Avalonia.Controls/Button.cs
@@ -289,8 +289,9 @@ protected override void OnKeyDown(KeyEventArgs e)
                     OnClick();
                     e.Handled = true;
                     break;
-
                 case Key.Space:
+                    // Avoid handling Space if the button isn't focused: a child TextBox might need it for text input
+                    if (IsFocused)
                     {
                         if (ClickMode == ClickMode.Press)
                         {
@@ -299,22 +300,21 @@ protected override void OnKeyDown(KeyEventArgs e)
 
                         IsPressed = true;
                         e.Handled = true;
-                        break;
                     }
-
+                    break;
                 case Key.Escape when Flyout != null:
                     // If Flyout doesn't have focusable content, close the flyout here
                     CloseFlyout();
                     break;
             }
-
             base.OnKeyDown(e);
         }
 
         /// <inheritdoc/>
         protected override void OnKeyUp(KeyEventArgs e)
         {
-            if (e.Key == Key.Space)
+            // Avoid handling Space if the button isn't focused: a child TextBox might need it for text input
+            if (e.Key == Key.Space && IsFocused)
             {
                 if (ClickMode == ClickMode.Release)
                 {


Test patch:
diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs
index 876adcc28fe..e3b974491aa 100644
--- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs
@@ -511,11 +511,39 @@ public void Button_CommandParameter_Does_Not_Change_While_Execution()
             (target as IClickableControl).RaiseClick();
         }
 
+        [Fact]
+        void Should_Not_Fire_Click_Event_On_Space_Key_When_It_Is_Not_Focus()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var raised = 0;
+                var target = new TextBox();
+                var button = new Button()
+                {
+                    Content = target,
+                };
+
+                var window = new Window { Content = button };
+                window.Show();
+
+                button.Click += (s, e) => ++raised;
+                target.Focus();
+                target.RaiseEvent(CreateKeyDownEvent(Key.Space));
+                target.RaiseEvent(CreateKeyUpEvent(Key.Space));
+                Assert.Equal(0, raised);
+            }
+        }
+
         private KeyEventArgs CreateKeyDownEvent(Key key, Interactive source = null)
         {
             return new KeyEventArgs { RoutedEvent = InputElement.KeyDownEvent, Key = key, Source = source };
         }
 
+        private KeyEventArgs CreateKeyUpEvent(Key key, Interactive source = null)
+        {
+            return new KeyEventArgs { RoutedEvent = InputElement.KeyUpEvent, Key = key, Source = source };
+        }
+
         private void RaisePointerPressed(Button button, int clickCount, MouseButton mouseButton, Point position)
         {
             _helper.Down(button, mouseButton, position, clickCount: clickCount);