diff --git a/src/components/BootstrapBlazor.Region/Components/SelectCity.razor b/src/components/BootstrapBlazor.Region/Components/SelectCity.razor
index f704ac89..b541d032 100644
--- a/src/components/BootstrapBlazor.Region/Components/SelectCity.razor
+++ b/src/components/BootstrapBlazor.Region/Components/SelectCity.razor
@@ -36,12 +36,12 @@
@code {
RenderFragment RenderItem(string item) =>
- @
+ @
OnSelectProvince(item)">@item
@foreach (var city in GetCities(item))
{
- - OnSelectCity(city)">@city
+ - OnSelectCity(city)">@city
}
;
diff --git a/src/components/BootstrapBlazor.Region/Components/SelectCity.razor.cs b/src/components/BootstrapBlazor.Region/Components/SelectCity.razor.cs
index 2a24fec9..d8918872 100644
--- a/src/components/BootstrapBlazor.Region/Components/SelectCity.razor.cs
+++ b/src/components/BootstrapBlazor.Region/Components/SelectCity.razor.cs
@@ -60,13 +60,20 @@ public partial class SelectCity
private readonly HashSet
_values = [];
private string? _searchText;
private bool _showSearch;
+ private IEnumerable _filteredProvinces = Provinces;
+ private HashSet? _provinceItems;
+ private Dictionary>? _cityPinyin;
private string? GetActiveClass(string item) => CssBuilder.Default()
.AddClass("active", _values.Contains(item) && IsMultiple)
.AddClass("active", CurrentValue == item && !IsMultiple)
- .AddClass("prev", !string.IsNullOrEmpty(_searchText) && StartsWith(PinyinService.GetFirstLetters(item), _searchText))
+ .AddClass("prev", IsPinyinMatch(item))
.Build();
+ private bool IsPinyinMatch(string item) => !string.IsNullOrEmpty(_searchText)
+ && _cityPinyin?.TryGetValue(item, out var pinyin) == true
+ && StartsWith(pinyin, _searchText);
+
///
///
///
@@ -84,6 +91,7 @@ protected override void OnParametersSet()
if (ShowSearch == false)
{
_searchText = "";
+ _filteredProvinces = Provinces;
}
}
@@ -123,7 +131,14 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
[JSInvokable]
public void TriggerSearch(string v)
{
- _searchText = v.ToUpperInvariant();
+ var searchText = v.ToUpperInvariant();
+ if (_searchText == searchText)
+ {
+ return;
+ }
+
+ _searchText = searchText;
+ _filteredProvinces = FilterProvinces(searchText);
StateHasChanged();
}
@@ -143,7 +158,7 @@ private async Task OnClearValue()
private RenderFragment RenderCities() => builder =>
{
- foreach (var item in GetProvinces())
+ foreach (var item in _filteredProvinces)
{
builder.AddContent(0, RenderItem(item));
}
@@ -190,19 +205,19 @@ private async Task OnSelectCity(string item)
}
}
- private HashSet GetProvinces()
+ private IEnumerable FilterProvinces(string searchText)
{
- if (string.IsNullOrEmpty(_searchText))
+ if (string.IsNullOrEmpty(searchText))
{
return Provinces;
}
- if (PinyinService.ContainsChinese(_searchText))
+ if (PinyinService.ContainsChinese(searchText))
{
- return [.. Provinces.Where(i => i.Contains(_searchText) || GetCities(i).Any(city => city.Contains(_searchText)))];
+ return Provinces.Where(i => i.Contains(searchText) || GetCities(i).Any(city => city.Contains(searchText))).ToArray();
}
- return [.. GenerateProvincePinYin().Where(i => FilterProvince(i, _searchText)).Select(i => i.Name)];
+ return GenerateProvincePinYin().Where(i => FilterProvince(i, searchText)).Select(i => i.Name).ToArray();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -211,16 +226,19 @@ private HashSet GetProvinces()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool StartsWith(HashSet source, string searchText) => source.Any(i => i.StartsWith(searchText, StringComparison.OrdinalIgnoreCase));
- private static HashSet? _provinceItems;
-
private HashSet GenerateProvincePinYin()
{
- _provinceItems ??= [.. Provinces.Select(i => new ProvinceItem()
+ if (_provinceItems is null)
{
- PinYin = PinyinService.GetFirstLetters(i),
- Name = i,
- Cities = GenerateCityPinYin(i)
- })];
+ _cityPinyin = [];
+ _provinceItems = [.. Provinces.Select(i => new ProvinceItem()
+ {
+ PinYin = PinyinService.GetFirstLetters(i),
+ Name = i,
+ Cities = GenerateCityPinYin(i)
+ })];
+ }
+
return _provinceItems;
}
@@ -231,10 +249,15 @@ private HashSet GenerateProvincePinYin()
_ => RegionService.GetCities(provinceName)
};
- private HashSet GenerateCityPinYin(string provinceName) => [.. GetCities(provinceName).Select(i => new CityItem()
+ private HashSet GenerateCityPinYin(string provinceName) => [.. GetCities(provinceName).Select(i =>
{
- PinYin = PinyinService.GetFirstLetters(i),
- Name = i
+ var pinyin = PinyinService.GetFirstLetters(i);
+ _cityPinyin![i] = pinyin;
+ return new CityItem()
+ {
+ PinYin = pinyin,
+ Name = i
+ };
})];
private static readonly HashSet Provinces = [
diff --git a/src/components/BootstrapBlazor.Region/Components/SelectCity.razor.js b/src/components/BootstrapBlazor.Region/Components/SelectCity.razor.js
index 311d51fe..6e460c5f 100644
--- a/src/components/BootstrapBlazor.Region/Components/SelectCity.razor.js
+++ b/src/components/BootstrapBlazor.Region/Components/SelectCity.razor.js
@@ -2,6 +2,7 @@ import Data from "../../BootstrapBlazor/modules/data.js"
import EventHandler from "../../BootstrapBlazor/modules/event-handler.js"
import Input from "../../BootstrapBlazor/modules/input.js"
import Popover from "../../BootstrapBlazor/modules/base-popover.js"
+import { debounce } from "../../BootstrapBlazor/modules/utility.js"
export function init(id, invoke, options) {
const el = document.getElementById(id);
@@ -37,9 +38,12 @@ const initSearch = region => {
const { el, invoke, options } = region;
const searchInput = el.querySelector(".search-text");
if (searchInput) {
- Input.composition(searchInput, async v => {
- await invoke.invokeMethodAsync(options.triggerSearch, v);
- });
+ const handler = debounce(async v => {
+ if (searchInput.isConnected && searchInput.value === v) {
+ await invoke.invokeMethodAsync(options.triggerSearch, v);
+ }
+ })
+ Input.composition(searchInput, handler);
}
const search = el.querySelector(".dropdown-menu-search .clear-icon");
diff --git a/test/UnitTestRegion/RegionTest.cs b/test/UnitTestRegion/RegionTest.cs
index f3a86db3..3b295b54 100644
--- a/test/UnitTestRegion/RegionTest.cs
+++ b/test/UnitTestRegion/RegionTest.cs
@@ -2,13 +2,43 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/
+using Bunit;
+
namespace UnitTestRegion;
-public class RegionTest
+public class RegionTest : IDisposable
{
+ private readonly Bunit.BunitContext _context = new();
+
+ public RegionTest()
+ {
+ _context.JSInterop.Mode = Bunit.JSRuntimeMode.Loose;
+ _context.Services.AddBootstrapBlazor();
+ _context.Services.AddBootstrapBlazorRegionService();
+ }
+
[Fact]
- public void Test1()
+ public async Task Search_Ok()
{
+ var cut = _context.Render();
+ await cut.InvokeAsync(() => cut.Instance.TriggerSearch("深圳"));
+ var province = Assert.Single(cut.FindAll(".bb-region-city-title"));
+ Assert.Equal("广东省", province.TextContent);
+
+ var pinyinService = _context.Services.GetRequiredService();
+ var pinyin = pinyinService.GetFirstLetters("深圳市").MaxBy(i => i.Length)!;
+ await cut.InvokeAsync(() => cut.Instance.TriggerSearch(pinyin));
+ Assert.Contains(cut.FindAll(".bb-region-city-title"), i => i.TextContent == "广东省");
+ Assert.Contains("prev", cut.FindAll("li").Single(i => i.TextContent == "深圳市").ClassList);
+
+ await cut.InvokeAsync(() => cut.Instance.TriggerSearch(""));
+ Assert.Equal(30, cut.FindAll(".bb-region-city-title").Count);
+ }
+
+ public void Dispose()
+ {
+ _context.Dispose();
+ GC.SuppressFinalize(this);
}
}
diff --git a/test/UnitTestRegion/UnitTestRegion.csproj b/test/UnitTestRegion/UnitTestRegion.csproj
index ae8644b7..367afdee 100644
--- a/test/UnitTestRegion/UnitTestRegion.csproj
+++ b/test/UnitTestRegion/UnitTestRegion.csproj
@@ -1,6 +1,7 @@
+