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:
@@ -14,11 +14,18 @@
|
|||||||
Name="PART_TextBox"
|
Name="PART_TextBox"
|
||||||
MinHeight="{TemplateBinding MinHeight}"
|
MinHeight="{TemplateBinding MinHeight}"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
iri:ClassHelper.ClassSource="{TemplateBinding}"
|
|
||||||
DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}"
|
DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}"
|
||||||
InnerLeftContent="{TemplateBinding InnerLeftContent}"
|
InnerLeftContent="{TemplateBinding InnerLeftContent}"
|
||||||
InnerRightContent="{TemplateBinding InnerRightContent}"
|
InnerRightContent="{TemplateBinding InnerRightContent}"
|
||||||
Watermark="{TemplateBinding Watermark}" />
|
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
|
<Popup
|
||||||
Name="PART_Popup"
|
Name="PART_Popup"
|
||||||
MaxHeight="{TemplateBinding MaxDropDownHeight}"
|
MaxHeight="{TemplateBinding MaxDropDownHeight}"
|
||||||
@@ -45,5 +52,13 @@
|
|||||||
</Panel>
|
</Panel>
|
||||||
</ControlTemplate>
|
</ControlTemplate>
|
||||||
</Setter>
|
</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>
|
</ControlTheme>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -1,12 +1,22 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
using Avalonia.Controls.Templates;
|
using Avalonia.Controls.Templates;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
|
using Irihi.Avalonia.Shared.Common;
|
||||||
using Irihi.Avalonia.Shared.Contracts;
|
using Irihi.Avalonia.Shared.Contracts;
|
||||||
|
|
||||||
namespace Ursa.Controls;
|
namespace Ursa.Controls;
|
||||||
|
|
||||||
public class AutoCompleteBox : Avalonia.Controls.AutoCompleteBox, IClearControl
|
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()
|
static AutoCompleteBox()
|
||||||
{
|
{
|
||||||
MinimumPrefixLengthProperty.OverrideDefaultValue<AutoCompleteBox>(0);
|
MinimumPrefixLengthProperty.OverrideDefaultValue<AutoCompleteBox>(0);
|
||||||
@@ -14,19 +24,44 @@ public class AutoCompleteBox : Avalonia.Controls.AutoCompleteBox, IClearControl
|
|||||||
|
|
||||||
public AutoCompleteBox()
|
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()
|
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)
|
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);
|
SetCurrentValue(IsDropDownOpenProperty, true);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnGotFocus(GotFocusEventArgs e)
|
protected override void OnGotFocus(GotFocusEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -35,6 +70,22 @@ public class AutoCompleteBox : Avalonia.Controls.AutoCompleteBox, IClearControl
|
|||||||
if (e.NavigationMethod == NavigationMethod.Pointer) return;
|
if (e.NavigationMethod == NavigationMethod.Pointer) return;
|
||||||
if (!this.GetTemplateChildren().Contains(e.Source)) return;
|
if (!this.GetTemplateChildren().Contains(e.Source)) return;
|
||||||
// If the focus is set by keyboard navigation, open the dropdown.
|
// 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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user