diff --git a/src/Blazored.Typeahead/BlazoredTypeahead.razor b/src/Blazored.Typeahead/BlazoredTypeahead.razor index 5541e22..c5f18f0 100644 --- a/src/Blazored.Typeahead/BlazoredTypeahead.razor +++ b/src/Blazored.Typeahead/BlazoredTypeahead.razor @@ -110,7 +110,6 @@ @onkeyup="HandleKeyup" @onkeyup:stopPropagation="@StopPropagation" @onkeyup:preventDefault="@PreventDefault" - @onblur="ResetControl" @onfocus="HandleInputFocus" autocomplete="off" @attributes="AdditionalAttributes" @@ -126,7 +125,7 @@ @if (!Disabled && EnableDropDown) { -
+
diff --git a/src/Blazored.Typeahead/BlazoredTypeahead.razor.cs b/src/Blazored.Typeahead/BlazoredTypeahead.razor.cs index 4482c81..ec23d68 100644 --- a/src/Blazored.Typeahead/BlazoredTypeahead.razor.cs +++ b/src/Blazored.Typeahead/BlazoredTypeahead.razor.cs @@ -281,17 +281,23 @@ private async Task HandleInputFocus() } private bool _resettingControl = false; - private async Task ResetControl() + private void ResetControl() { if (!_resettingControl) { _resettingControl = true; - await Task.Delay(200); Initialize(); _resettingControl = false; } } + [JSInvokable("ResetControlBlur")] + public void ResetControlBlur() + { + ResetControl(); + StateHasChanged(); + } + private async Task ShowMaximumSuggestions() { if (_resettingControl) @@ -315,6 +321,7 @@ private async Task ShowMaximumSuggestions() IsSearching = false; await InvokeAsync(StateHasChanged); } + await HookOutsideClick(); } private string GetSelectedSuggestionClass(TItem item, int index) @@ -356,10 +363,16 @@ private async void Search(Object source, ElapsedEventArgs e) IsSearching = false; IsShowingSuggestions = true; + await HookOutsideClick(); SelectedIndex = 0; await InvokeAsync(StateHasChanged); } + private async Task HookOutsideClick() + { + await JSRuntime.OnOutsideClick(_searchInput, this, "ResetControlBlur", true); + } + private async Task SelectResult(TItem item) { var value = ConvertMethod(item); diff --git a/src/Blazored.Typeahead/Interop.cs b/src/Blazored.Typeahead/Interop.cs index f9e0bf8..930a1dc 100644 --- a/src/Blazored.Typeahead/Interop.cs +++ b/src/Blazored.Typeahead/Interop.cs @@ -15,5 +15,10 @@ internal static ValueTask AddKeyDownEventListener(IJSRuntime jsRuntime, { return jsRuntime.InvokeAsync("blazoredTypeahead.addKeyDownEventListener", element); } + + internal static ValueTask OnOutsideClick(this IJSRuntime jsRuntime, ElementReference element, object caller, string methodName, bool clearOnFire = false) + { + return jsRuntime.InvokeAsync("blazoredTypeahead.onOutsideClick", element, DotNetObjectReference.Create(caller), methodName, clearOnFire); + } } } diff --git a/src/Blazored.Typeahead/wwwroot/blazored-typeahead.js b/src/Blazored.Typeahead/wwwroot/blazored-typeahead.js index 9475cdd..4c9939d 100644 --- a/src/Blazored.Typeahead/wwwroot/blazored-typeahead.js +++ b/src/Blazored.Typeahead/wwwroot/blazored-typeahead.js @@ -1,5 +1,7 @@ "use strict"; +var onOutsideClickFunctions = {}; + window.blazoredTypeahead = { assemblyname: "Blazored.Typeahead", setFocus: function (element) { @@ -16,5 +18,64 @@ window.blazoredTypeahead = { } }); } + }, + onOutsideClickClear: function (element) { + + if (element == null) { + return; + } + + var bId = ""; + for (var clearCount = 0; clearCount < element.attributes.length; clearCount++) { + var a = element.attributes[clearCount]; + if (a.name.startsWith('_bl_')) { + bId = a.name; + break; + } + } + + var func = onOutsideClickFunctions[bId]; + if (func == null || func == "undefined") { + return; + } + window.removeEventListener("click", func); + onOutsideClickFunctions[bId] = null; + }, + onOutsideClick: function (searchTextElement, dotnetRef, methodName, clearOnFire) { + + if (searchTextElement == null) { + return; + } + + var bId = "";//get the blazor internal ID to distinguish different components + for (var clearCount = 0; clearCount < searchTextElement.attributes.length; clearCount++) { + var a = searchTextElement.attributes[clearCount]; + if (a.name.startsWith('_bl_')) { + bId = a.name; + break; + } + } + + blazoredTypeahead.onOutsideClickClear(searchTextElement); //clean up just in case + + var func = (e) => { + var parent = e.target; + while (parent != null) { + if (parent.classList != null && parent.classList.contains('blazored-typeahead')) { + var hasSearch = parent.contains(searchTextElement); //check if this is the same typeahead parent element + if (hasSearch) { + return; //we're still in the search so don't fire + } + } + parent = parent.parentNode; + } + + dotnetRef.invokeMethodAsync(methodName); + if (clearOnFire) { //could also add a check to see if the search element is missing on the DOM to force cleaning up the function? + blazoredTypeahead.onOutsideClickClear(searchTextElement); + } + }; + onOutsideClickFunctions[bId] = func; //save a reference to the click function + window.addEventListener("click", func); } };