diff --git a/src/BootstrapBlazor.Shared/Components/Samples/Selects.razor.cs b/src/BootstrapBlazor.Shared/Components/Samples/Selects.razor.cs index 26c3cdfe8b2..b5acb838af6 100644 --- a/src/BootstrapBlazor.Shared/Components/Samples/Selects.razor.cs +++ b/src/BootstrapBlazor.Shared/Components/Samples/Selects.razor.cs @@ -61,18 +61,13 @@ protected override void OnInitialized() Foos = Foo.GenerateFoo(LocalizerFoo); } - private IEnumerable OnSearchTextChanged(string searchText) - { - return Foos.Where(i => i.Name!.Contains(searchText, StringComparison.OrdinalIgnoreCase)).Select(i => new SelectedItem(i.Name!, i.Name!)); - } - private async Task> OnQueryAsync(VirtualizeQueryOption option) { await Task.Delay(200); var items = Foos; if (!string.IsNullOrEmpty(option.SearchText)) { - items = items.Where(i => i.Name!.Contains(option.SearchText, StringComparison.OrdinalIgnoreCase)).ToList(); + items = Foos.Where(i => i.Name!.Contains(option.SearchText, StringComparison.OrdinalIgnoreCase)).ToList(); } return new QueryData { diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index eabc92208a5..f0ce0af17eb 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@  - 9.1.0 + 9.1.1 diff --git a/src/BootstrapBlazor/Components/Select/Select.razor b/src/BootstrapBlazor/Components/Select/Select.razor index 7161f79ac6b..f25cc0d349d 100644 --- a/src/BootstrapBlazor/Components/Select/Select.razor +++ b/src/BootstrapBlazor/Components/Select/Select.razor @@ -13,19 +13,16 @@ @Options - @{ - ResetSelectedItem(); - } @@ -59,11 +56,11 @@ @if (ShowSearch) {
- +
} - @foreach (var itemGroup in _dataSource.GroupBy(i => i.GroupName)) + @foreach (var itemGroup in Rows.GroupBy(i => i.GroupName)) { if (!string.IsNullOrEmpty(itemGroup.Key)) { @@ -81,7 +78,7 @@ @RenderRow(item) } } - @if (!_dataSource.Any()) + @if (Rows.Count == 0) { } diff --git a/src/BootstrapBlazor/Components/Select/Select.razor.cs b/src/BootstrapBlazor/Components/Select/Select.razor.cs index 928ea57d2b4..8629acb5ac5 100644 --- a/src/BootstrapBlazor/Components/Select/Select.razor.cs +++ b/src/BootstrapBlazor/Components/Select/Select.razor.cs @@ -60,8 +60,6 @@ public partial class Select : ISelect, IModelEqualityComparer private readonly List _children = []; - private readonly List _dataSource = []; - /// /// 获得/设置 右侧清除图标 默认 fa-solid fa-angle-up /// @@ -73,7 +71,6 @@ public partial class Select : ISelect, IModelEqualityComparer /// 获得/设置 搜索文本发生变化时回调此方法 /// [Parameter] - [NotNull] public Func>? OnSearchTextChanged { get; set; } /// @@ -213,6 +210,68 @@ public partial class Select : ISelect, IModelEqualityComparer private bool _init = true; + private List? _itemsCache; + + private ItemsProviderResult _result; + + private List Rows + { + get + { + _itemsCache ??= string.IsNullOrEmpty(SearchText) ? GetRowsByItems() : GetRowsBySearch(); + return _itemsCache; + } + } + + private SelectedItem? SelectedRow + { + get + { + SelectedItem ??= GetSelectedRow(); + return SelectedItem; + } + } + + private SelectedItem? GetSelectedRow() + { + var item = Rows.Find(Match) + ?? Rows.Find(i => i.Active) + ?? Rows.Where(i => !i.IsDisabled).FirstOrDefault() + ?? GetVirtualizeItem(); + + if (item != null) + { + if (_init && DisableItemChangedWhenFirstRender) + { + + } + else + { + _ = SelectedItemChanged(item); + _init = false; + } + } + return item; + } + + private List GetRowsByItems() + { + var items = new List(); + items.AddRange(Items); + items.AddRange(_children); + return items; + } + + private List GetRowsBySearch() + { + var items = OnSearchTextChanged?.Invoke(SearchText) ?? FilterBySearchText(GetRowsByItems()); + return items.ToList(); + } + + private IEnumerable FilterBySearchText(IEnumerable source) => string.IsNullOrEmpty(SearchText) + ? source + : source.Where(i => i.Text.Contains(SearchText, StringComparison)); + /// /// /// @@ -221,7 +280,6 @@ protected override void OnParametersSet() base.OnParametersSet(); Items ??= []; - OnSearchTextChanged ??= text => Items.Where(i => i.Text.Contains(text, StringComparison)); PlaceHolder ??= Localizer[nameof(PlaceHolder)]; NoSearchDataText ??= Localizer[nameof(NoSearchDataText)]; DropdownIcon ??= IconTheme.GetIconByKey(ComponentIcons.SelectDropdownIcon); @@ -233,6 +291,9 @@ protected override void OnParametersSet() var item = NullableUnderlyingType == null ? "" : PlaceHolder; Items = ValueType.ToSelectList(string.IsNullOrEmpty(item) ? null : new SelectedItem("", item)); } + + _itemsCache = null; + SelectedItem = null; } /// @@ -240,9 +301,7 @@ protected override void OnParametersSet() /// private int TotalCount { get; set; } - private IEnumerable? VirtualItems { get; set; } - - private List GetVirtualItems() => (VirtualItems ?? Items).ToList(); + private List GetVirtualItems() => FilterBySearchText(GetRowsByItems()).ToList(); /// /// 虚拟滚动数据加载回调方法 @@ -259,8 +318,9 @@ private async ValueTask> LoadItems(ItemsProvid var data = await OnQueryAsync(new() { StartIndex = request.StartIndex, Count = count, SearchText = SearchText }); TotalCount = data.TotalCount; - VirtualItems = data.Items ?? []; - return new ItemsProviderResult(VirtualItems, TotalCount); + var items = data.Items ?? []; + _result = new ItemsProviderResult(items, TotalCount); + return _result; int GetCountByTotal() => TotalCount == 0 ? request.Count : Math.Min(request.Count, TotalCount - request.StartIndex); } @@ -268,17 +328,13 @@ private async ValueTask> LoadItems(ItemsProvid private async Task SearchTextChanged(string val) { SearchText = val; - if (OnQueryAsync == null) - { - // 通过 Items 提供数据 - VirtualItems = OnSearchTextChanged(SearchText); - } - else + _itemsCache = null; + + if (OnQueryAsync != null) { // 通过 ItemProvider 提供数据 await VirtualizeElement.RefreshDataAsync(); } - StateHasChanged(); } /// @@ -295,7 +351,6 @@ protected override bool TryParseValueFromString(string value, [MaybeNullWhen(fal private bool TryParseSelectItem(string value, [MaybeNullWhen(false)] out TValue result, out string? validationErrorMessage) { SelectedItem = Items.FirstOrDefault(i => i.Value == value) - ?? VirtualItems?.FirstOrDefault(i => i.Value == value) ?? GetVirtualizeItem(); // support SelectedItem? type @@ -313,51 +368,6 @@ private bool TryParseSelectItem(string value, [MaybeNullWhen(false)] out TValue : new SelectedItem(CurrentValueAsString, DefaultVirtualizeItemText ?? CurrentValueAsString); } - private void ResetSelectedItem() - { - _dataSource.Clear(); - - if (string.IsNullOrEmpty(SearchText)) - { - _dataSource.AddRange(Items); - _dataSource.AddRange(_children); - - if (VirtualItems != null) - { - _dataSource.AddRange(VirtualItems); - } - - SelectedItem = _dataSource.Find(Match) - ?? _dataSource.Find(i => i.Active) - ?? _dataSource.Where(i => !i.IsDisabled).FirstOrDefault() - ?? GetVirtualizeItem(); - - if (SelectedItem != null) - { - if (_init && DisableItemChangedWhenFirstRender) - { - - } - else - { - _ = SelectedItemChanged(SelectedItem); - _init = false; - } - } - } - else if (IsVirtualize) - { - if (Items.Any()) - { - VirtualItems = OnSearchTextChanged(SearchText); - } - } - else - { - _dataSource.AddRange(OnSearchTextChanged(SearchText)); - } - } - /// /// /// @@ -374,12 +384,11 @@ private void ResetSelectedItem() [JSInvokable] public async Task ConfirmSelectedItem(int index) { - var ds = string.IsNullOrEmpty(SearchText) - ? _dataSource - : OnSearchTextChanged(SearchText); - var item = ds.ElementAt(index); - await OnClickItem(item); - StateHasChanged(); + if (index < Rows.Count) + { + await OnClickItem(Rows[index]); + StateHasChanged(); + } } /// @@ -483,24 +492,15 @@ private async Task OnClearValue() } SelectedItem? item; - if (IsVirtualize) + if (OnQueryAsync != null) { - if (VirtualizeElement != null) - { - await VirtualizeElement.RefreshDataAsync(); - item = VirtualItems!.FirstOrDefault(); - } - else - { - VirtualItems = Items; - item = Items.FirstOrDefault(); - } + await VirtualizeElement.RefreshDataAsync(); + item = _result.Items.FirstOrDefault(); } else { item = Items.FirstOrDefault(); } - if (item != null) { await SelectedItemChanged(item); diff --git a/test/UnitTest/Components/SelectTest.cs b/test/UnitTest/Components/SelectTest.cs index 25b73e47196..96d6714e653 100644 --- a/test/UnitTest/Components/SelectTest.cs +++ b/test/UnitTest/Components/SelectTest.cs @@ -14,7 +14,7 @@ namespace UnitTest.Components; public class SelectTest : BootstrapBlazorTestBase { [Fact] - public void OnSearchTextChanged_Null() + public async Task OnSearchTextChanged_Null() { var cut = Context.RenderComponent(pb => { @@ -30,7 +30,7 @@ public void OnSearchTextChanged_Null() }); var ctx = cut.FindComponent>(); - ctx.InvokeAsync(async () => + await ctx.InvokeAsync(async () => { await ctx.Instance.ConfirmSelectedItem(0); @@ -44,9 +44,28 @@ public void OnSearchTextChanged_Null() pb.Add(a => a.OnBeforeSelectedItemChange, item => Task.FromResult(false)); pb.Add(a => a.OnSelectedItemChanged, item => Task.CompletedTask); }); - ctx.InvokeAsync(() => ctx.Instance.ConfirmSelectedItem(0)); + await ctx.InvokeAsync(() => ctx.Instance.ConfirmSelectedItem(0)); ctx.Instance.ClearSearchText(); + + ctx.SetParametersAndRender(pb => + { + pb.Add(a => a.OnBeforeSelectedItemChange, null); + pb.Add(a => a.OnSelectedItemChanged, null); + pb.Add(a => a.OnSearchTextChanged, text => + { + return new List() + { + new("1", "Test1") + }; + }); + }); + + await ctx.InvokeAsync(() => + { + ctx.Find(".search-text").Input("T"); + }); + cut.DoesNotContain("Test2"); } [Fact] @@ -719,12 +738,8 @@ public async Task IsVirtualize_OnQueryAsync_Clearable_Ok() return Task.FromResult(new QueryData() { Items = string.IsNullOrEmpty(searchText) - ? new SelectedItem[] - { - new("", "All"), - new("1", "Test1"), - new("2", "Test2") - } : [new("2", "Test2")], + ? [new("", "All"), new("1", "Test1"), new("2", "Test2")] + : [new("2", "Test2")], TotalCount = string.IsNullOrEmpty(searchText) ? 2 : 1 }); });