XYFocus should skip effectively disabled controls (#20241)

* Added failing unit test

* Changed IsValidCandidate to check effective values

---------

Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
This commit is contained in:
IanRawley
2025-12-16 09:09:19 +13:00
committed by GitHub
parent 367d8b293b
commit 7a340a6579
2 changed files with 41 additions and 1 deletions
@@ -65,7 +65,7 @@ public partial class XYFocus
private static bool IsValidCandidate(InputElement candidate, KeyDeviceType? inputKeyDeviceType)
{
return candidate.Focusable && candidate.IsEnabled && candidate.IsVisible
return candidate.Focusable && candidate.IsEffectivelyEnabled && candidate.IsEffectivelyVisible
// Only allow candidate focus, if original key device type could focus it.
&& XYFocusHelpers.IsAllowedXYNavigationMode(candidate, inputKeyDeviceType);
}
@@ -459,4 +459,44 @@ public class KeyboardNavigationTests_XY : ScopedTestBase
Assert.Null(KeyboardNavigationHandler.GetNext(current, NavigationDirection.Down));
}
[Fact]
public void XYFocus_Skips_Effectively_Disabled_Controls()
{
using var _ = UnitTestApplication.Start(TestServices.FocusableWindow);
var current = new TestControl() { Height = 20, Width = 20, IsEnabled = true, IsVisible = true, Focusable = true, ShouldEnable = true};
var disabled = new TestControl() { Height = 20, Width = 20, IsEnabled = true, IsVisible = true, Focusable = true, ShouldEnable = false };
var candidate = new TestControl() { Height = 20, Width = 20, IsEnabled = true, IsVisible = true, Focusable = true, ShouldEnable = true };
var parent = new StackPanel
{
Orientation = Orientation.Vertical,
Spacing = 20,
Children = { current, disabled, candidate }
};
var window = new Window
{
[XYFocus.NavigationModesProperty] = XYFocusNavigationModes.Enabled,
Content = parent,
Height = 300
};
window.Show();
Assert.Equal(candidate, KeyboardNavigationHandler.GetNext(current, NavigationDirection.Down));
}
private class TestControl : Decorator
{
private bool _shouldEnable;
public bool ShouldEnable
{
get => _shouldEnable;
set { _shouldEnable = value; UpdateIsEffectivelyEnabled(); }
}
protected override bool IsEnabledCore => IsEnabled && _shouldEnable;
}
}