From 8f5179abdf20c3b9f701c893f4dd90ac7b3a4c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikul=C3=A1=C5=A1=20Hobl=C3=ADk?= Date: Mon, 5 Sep 2022 20:27:37 +0200 Subject: [PATCH] [HxSearchBox} Keyboard navigation adjustments + HightLightFirstSuggestion #348 --- .../Havit.Blazor.Components.Web.Bootstrap.xml | 15 +++++++ .../Forms/SearchBox/HxSearchBox.nongeneric.cs | 1 + .../Forms/SearchBox/HxSearchBox.razor.cs | 41 ++++++++++++++----- .../Forms/SearchBox/SearchBoxSettings.cs | 5 +++ 4 files changed, 51 insertions(+), 11 deletions(-) diff --git a/Havit.Blazor.Components.Web.Bootstrap.Documentation/XmlDoc/Havit.Blazor.Components.Web.Bootstrap.xml b/Havit.Blazor.Components.Web.Bootstrap.Documentation/XmlDoc/Havit.Blazor.Components.Web.Bootstrap.xml index 7a6623a76..5ce553033 100755 --- a/Havit.Blazor.Components.Web.Bootstrap.Documentation/XmlDoc/Havit.Blazor.Components.Web.Bootstrap.xml +++ b/Havit.Blazor.Components.Web.Bootstrap.Documentation/XmlDoc/Havit.Blazor.Components.Web.Bootstrap.xml @@ -5077,11 +5077,21 @@ Hides the search icon when used! + + + If true, the first suggestion is highlighted until another is chosen by the user. + + Shows whether the has been below minimum required length recently (before data provider loading is completed). + + + Input's index for the keyboard navigation. If this is the current index, then no item is selected. + + If the is empty, we don't want to display anything when nothing (or below the minimum amount of characters) is typed into the input. @@ -5163,6 +5173,11 @@ Additional CSS classes for the search box input. + + + If true, the first suggestion is highlighted until another is chosen by the user. + + Settings for the component. diff --git a/Havit.Blazor.Components.Web.Bootstrap/Forms/SearchBox/HxSearchBox.nongeneric.cs b/Havit.Blazor.Components.Web.Bootstrap/Forms/SearchBox/HxSearchBox.nongeneric.cs index b8e89da6d..262a92b4e 100644 --- a/Havit.Blazor.Components.Web.Bootstrap/Forms/SearchBox/HxSearchBox.nongeneric.cs +++ b/Havit.Blazor.Components.Web.Bootstrap/Forms/SearchBox/HxSearchBox.nongeneric.cs @@ -19,6 +19,7 @@ static HxSearchBox() ClearIcon = BootstrapIcon.XLg, MinimumLength = 2, Delay = 300, + HighlightFirstSuggestion = true }; } } diff --git a/Havit.Blazor.Components.Web.Bootstrap/Forms/SearchBox/HxSearchBox.razor.cs b/Havit.Blazor.Components.Web.Bootstrap/Forms/SearchBox/HxSearchBox.razor.cs index 8a064c45c..89f5a3e7e 100644 --- a/Havit.Blazor.Components.Web.Bootstrap/Forms/SearchBox/HxSearchBox.razor.cs +++ b/Havit.Blazor.Components.Web.Bootstrap/Forms/SearchBox/HxSearchBox.razor.cs @@ -201,6 +201,12 @@ public partial class HxSearchBox : IAsyncDisposable /// [Parameter] public RenderFragment InputGroupEndTemplate { get; set; } + /// + /// If true, the first suggestion is highlighted until another is chosen by the user. + /// + [Parameter] public bool? HighlightFirstSuggestion { get; set; } + protected bool HighlightFirstSuggestionEffective => this.HighlightFirstSuggestion ?? GetSettings()?.HighlightFirstSuggestion ?? GetDefaults()?.HighlightFirstSuggestion ?? throw new InvalidOperationException(nameof(HighlightFirstSuggestion) + " default for " + nameof(HxSearchBox) + " has to be set."); + private string dropdownToggleElementId = "hx" + Guid.NewGuid().ToString("N"); private string dropdownId = "hx" + Guid.NewGuid().ToString("N"); private List searchResults = new(); @@ -276,7 +282,15 @@ protected async Task UpdateSuggestionsAsync() dataProviderInProgress = false; - focusedItemIndex = default; // KeyboardNavigation + // KeyboardNavigation + if (HighlightFirstSuggestionEffective) + { + focusedItemIndex = 0; // First item in the searchResults collection. + } + else + { + focusedItemIndex = InputKeyboardNavigationIndex; + } searchResults = result?.Data.ToList(); @@ -329,18 +343,23 @@ protected async Task HandleTextQueryValueChanged(string newTextQuery) private const string EnterKeyCode = "Enter"; private const string NumpadEnterKeyCode = "NumpadEnter"; + /// + /// Input's index for the keyboard navigation. If this is the current index, then no item is selected. + /// + private const int InputKeyboardNavigationIndex = -1; + private bool HasItemFocus(TItem item) { - TItem focusedItem = GetItemByIndex(focusedItemIndex); - - if ((focusedItem is not null) && (!focusedItem.Equals(default))) - { - return item.Equals(focusedItem); - } - else + if (focusedItemIndex > InputKeyboardNavigationIndex && focusedItemIndex < GetFreeTextItemIndex()) { - return false; + TItem focusedItem = GetItemByIndex(focusedItemIndex); + if ((focusedItem is not null) && (!focusedItem.Equals(default))) + { + return item.Equals(focusedItem); + } } + + return false; } private bool HasFreeTextItemFocus() @@ -368,7 +387,7 @@ private async Task UpdateFocusedItem(KeyboardEventArgs keyboardEventArgs) if (keyboardEventArgs.Code == ArrowUpKeyCode) { int previousItemIndex = focusedItemIndex - 1; - if (previousItemIndex >= -1) // If the index is -1, no item is focused. + if (previousItemIndex >= InputKeyboardNavigationIndex) // If the index equals InputKeyboardNavigationIndex, no item is focused. { focusedItemIndex = previousItemIndex; } @@ -376,7 +395,7 @@ private async Task UpdateFocusedItem(KeyboardEventArgs keyboardEventArgs) else if (keyboardEventArgs.Code == ArrowDownKeyCode) { int nextItemIndex = focusedItemIndex + 1; - if (nextItemIndex < GetFreeTextItemIndex()) // If the index equals GetFreeTextItemIndex(), then the freetext item is selected. + if (nextItemIndex <= GetFreeTextItemIndex()) // If the index equals GetFreeTextItemIndex(), then the freetext item is selected. { focusedItemIndex = nextItemIndex; } diff --git a/Havit.Blazor.Components.Web.Bootstrap/Forms/SearchBox/SearchBoxSettings.cs b/Havit.Blazor.Components.Web.Bootstrap/Forms/SearchBox/SearchBoxSettings.cs index 9f9a7efbe..f36c98ece 100644 --- a/Havit.Blazor.Components.Web.Bootstrap/Forms/SearchBox/SearchBoxSettings.cs +++ b/Havit.Blazor.Components.Web.Bootstrap/Forms/SearchBox/SearchBoxSettings.cs @@ -44,4 +44,9 @@ public class SearchBoxSettings /// Additional CSS classes for the search box input. /// public string InputCssClass { get; set; } + + /// + /// If true, the first suggestion is highlighted until another is chosen by the user. + /// + public bool? HighlightFirstSuggestion { get; set; } }