fix(ComboBox): restore keyboard reselection for v6.1.4

This commit is contained in:
chinboy
2026-08-18 18:08:38 +08:00
parent 08002681a0
commit 287c697034
4 changed files with 144 additions and 2 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<AvaloniaVersion>12.1.1</AvaloniaVersion>
<AtomUIVersion>6.1.3</AtomUIVersion>
<AtomUIVersion>6.1.4</AtomUIVersion>
</PropertyGroup>
</Project>
@@ -2,6 +2,11 @@
本文档记录 ComboBox 控件级设计、API、主题契约、Token 和实现结构的变化。它不替代仓库根目录 `CHANGELOG.md`,也不作为正式版本发布说明。
## 2026-08-18
- Behavior
- Keep keyboard input on a non-editable ComboBox when reopening its popup so consecutive `Down` and `Enter` selections continue to update the selected item.
## 2026-07-05
- Behavior
@@ -910,7 +910,16 @@ public class ComboBox : AvaloniaComboBox,
private void HandlePopupOpened(object? sender, EventArgs e)
{
RefreshFilteredItemVisibility();
FocusEditableTextBox(NavigationMethod.Unspecified);
if (IsEditable)
{
FocusEditableTextBox(NavigationMethod.Unspecified);
}
else if (TopLevel.GetTopLevel(this)?.FocusManager.GetFocusedElement() is Visual focusedVisual &&
_popup?.IsInsidePopup(focusedVisual) == true)
{
Focus(NavigationMethod.Unspecified);
}
}
private void HandleEditableTextBoxTextInput(TextInputEventArgs e)
@@ -0,0 +1,128 @@
using System.Reflection;
using AtomUI.Controls;
using AtomUI.Controls.Primitives;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Headless;
using Avalonia.Input;
using Avalonia.Threading;
using Shouldly;
using Xunit;
using AvaloniaWindow = Avalonia.Controls.Window;
using AtomUIComboBox = AtomUI.Desktop.Controls.ComboBox;
namespace AtomUI.Desktop.Controls.Tests.ComboBox;
public class ComboBoxKeyboardNavigationTests
{
static ComboBoxKeyboardNavigationTests()
{
AvaloniaTestApp.EnsureInitialized();
}
[Fact]
public void NonEditable_ComboBox_Allows_Consecutive_Keyboard_Selections()
{
var comboBox = new AtomUIComboBox
{
Width = 240,
ItemsSource = new[] { "Alpha", "Beta", "Gamma" },
IsMotionEnabled = false
};
ShowInWindow(comboBox, window =>
{
comboBox.Focus(NavigationMethod.Tab).ShouldBeTrue();
Dispatcher.UIThread.RunJobs();
SelectNextItem(window, comboBox, 0);
comboBox.Focus(NavigationMethod.Tab).ShouldBeTrue();
comboBox.IsDropDownOpen = true;
Dispatcher.UIThread.RunJobs();
TopLevel.GetTopLevel(comboBox)?.FocusManager.GetFocusedElement()
.ShouldBeSameAs(comboBox);
PressKey(window, Key.Down, PhysicalKey.ArrowDown);
Dispatcher.UIThread.RunJobs();
PressKey(window, Key.Enter, PhysicalKey.Enter);
Dispatcher.UIThread.RunJobs();
comboBox.SelectedIndex.ShouldBe(1);
comboBox.IsDropDownOpen.ShouldBeFalse();
});
}
private static void SelectNextItem(AvaloniaWindow window, AtomUIComboBox comboBox, int expectedIndex)
{
comboBox.IsDropDownOpen = true;
Dispatcher.UIThread.RunJobs();
PressKey(window, Key.Down, PhysicalKey.ArrowDown);
Dispatcher.UIThread.RunJobs();
PressKey(window, Key.Enter, PhysicalKey.Enter);
Dispatcher.UIThread.RunJobs();
comboBox.SelectedIndex.ShouldBe(expectedIndex);
comboBox.IsDropDownOpen.ShouldBeFalse();
}
private static void PressKey(AvaloniaWindow window, Key key, PhysicalKey physicalKey)
{
window.KeyPress(key, RawInputModifiers.None, physicalKey, null);
}
private static void ShowInWindow(Control content, Action<AvaloniaWindow> assertion)
{
var window = new AvaloniaWindow
{
Width = 360,
Height = 220,
Content = CreatePopupOverlayHost(content)
};
try
{
window.Show();
Dispatcher.UIThread.RunJobs();
content.ApplyTemplate();
content.Measure(Size.Infinity);
content.Arrange(new Rect(content.DesiredSize));
Dispatcher.UIThread.RunJobs();
assertion(window);
}
finally
{
window.Close();
}
}
private static VisualLayerManager CreatePopupOverlayHost(Control content)
{
var overlayPanel = new ScopeAwareOverlayLayerPanel
{
Width = 360,
Height = 220
};
overlayPanel.Children.Add(content);
var visualLayerManager = new VisualLayerManager
{
Child = overlayPanel
};
EnablePopupOverlayLayer(visualLayerManager);
return visualLayerManager;
}
private static void EnablePopupOverlayLayer(VisualLayerManager visualLayerManager)
{
var property = typeof(VisualLayerManager).GetProperty(
"EnablePopupOverlayLayer",
BindingFlags.Instance | BindingFlags.NonPublic);
property.ShouldNotBeNull();
property.SetValue(visualLayerManager, true);
}
}