Improve AutoCompleteBox focus behavior for 11.3.x (#761)

* feat: improve AutoCompleteBox focus behavior for 11.3.x

* fix: open dropdown on tab navigation regardless of navigation method

* feat: update per copilot comment.

* feat: add a flag to control focus.

* fix: fix naming.
This commit is contained in:
Dong Bin
2025-09-10 21:30:56 +08:00
committed by GitHub
parent 7e35b880bb
commit e163d671ab
2 changed files with 71 additions and 5 deletions

View File

@@ -14,11 +14,18 @@
Name="PART_TextBox"
MinHeight="{TemplateBinding MinHeight}"
VerticalAlignment="Stretch"
iri:ClassHelper.ClassSource="{TemplateBinding}"
DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}"
InnerLeftContent="{TemplateBinding InnerLeftContent}"
InnerRightContent="{TemplateBinding InnerRightContent}"
Watermark="{TemplateBinding Watermark}" />
<Button
Name="PART_ClearButton"
HorizontalAlignment="Right"
Margin="{DynamicResource TextBoxContentPadding}"
Command="{Binding $parent[u:AutoCompleteBox].Clear}"
Content="{DynamicResource IconButtonClearData}"
IsVisible="False"
Theme="{StaticResource InnerIconButton}" />
<Popup
Name="PART_Popup"
MaxHeight="{TemplateBinding MaxDropDownHeight}"
@@ -45,5 +52,13 @@
</Panel>
</ControlTemplate>
</Setter>
<Style Selector="^.clearButton, ^.ClearButton">
<Style Selector="^:focus-within:not(:empty) /template/ Button#PART_ClearButton">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="^:pointerover:not(:empty) /template/ Button#PART_ClearButton">
<Setter Property="IsVisible" Value="True" />
</Style>
</Style>
</ControlTheme>
</ResourceDictionary>

View File

@@ -1,12 +1,22 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
using Irihi.Avalonia.Shared.Common;
using Irihi.Avalonia.Shared.Contracts;
namespace Ursa.Controls;
public class AutoCompleteBox : Avalonia.Controls.AutoCompleteBox, IClearControl
{
// ReSharper disable once InconsistentNaming
private const string PART_TextBox = "PART_TextBox";
private bool _closeBySelectionFlag;
private TextBox? _textbox;
static AutoCompleteBox()
{
MinimumPrefixLengthProperty.OverrideDefaultValue<AutoCompleteBox>(0);
@@ -14,18 +24,43 @@ public class AutoCompleteBox : Avalonia.Controls.AutoCompleteBox, IClearControl
public AutoCompleteBox()
{
AddHandler(PointerPressedEvent, OnBoxPointerPressed, RoutingStrategies.Tunnel);
AddHandler(PointerReleasedEvent, OnCurrentPointerReleased, RoutingStrategies.Tunnel);
}
private void OnCurrentPointerReleased(object sender, PointerReleasedEventArgs e)
{
var source = (e.Source as Control).FindAncestorOfType<ListBoxItem>();
if (source is not null)
{
_closeBySelectionFlag = true;
}
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_textbox?.RemoveHandler(PointerPressedEvent, OnBoxPointerPressed);
_textbox = e.NameScope.Find<TextBox>(PART_TextBox);
_textbox?.AddHandler(PointerPressedEvent, OnBoxPointerPressed, handledEventsToo: true);
PseudoClasses.Set(PseudoClassName.PC_Empty, string.IsNullOrEmpty(Text));
}
public void Clear()
{
SetCurrentValue(SelectedItemProperty, null);
// Note: this method only resets Text to null.
// By default, AutoCompleteBox will clear the SelectedItem when Text is set to null.
// But user can use custom Predicate to control the behavior when Text is set to null.
SetCurrentValue(TextProperty, null);
}
private void OnBoxPointerPressed(object? sender, PointerPressedEventArgs e)
{
if (Equals(sender, this) && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed && IsDropDownOpen == false)
if (Equals(sender, _textbox)
&& e.GetCurrentPoint(this).Properties.IsLeftButtonPressed
&& IsDropDownOpen == false)
{
SetCurrentValue(IsDropDownOpenProperty, true);
}
}
protected override void OnGotFocus(GotFocusEventArgs e)
@@ -35,6 +70,22 @@ public class AutoCompleteBox : Avalonia.Controls.AutoCompleteBox, IClearControl
if (e.NavigationMethod == NavigationMethod.Pointer) return;
if (!this.GetTemplateChildren().Contains(e.Source)) return;
// If the focus is set by keyboard navigation, open the dropdown.
if (IsDropDownOpen == false) SetCurrentValue(IsDropDownOpenProperty, true);
if (!_closeBySelectionFlag && IsDropDownOpen == false)
{
SetCurrentValue(IsDropDownOpenProperty, true);
}
_closeBySelectionFlag = false;
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == TextProperty)
{
var value = change.GetNewValue<string?>();
PseudoClasses.Set(PseudoClassName.PC_Empty, string.IsNullOrEmpty(value));
}
}
}