diff --git a/src/BootstrapBlazor.Server/Components/Samples/SelectObjects.razor b/src/BootstrapBlazor.Server/Components/Samples/SelectObjects.razor index 8b7e09c12ff..4940a4b9656 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/SelectObjects.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/SelectObjects.razor @@ -7,7 +7,10 @@

@Localizer["Intro"]

- +
+ @((MarkupString)Localizer["NormalDesc"].Value) +
+ diff --git a/src/BootstrapBlazor.Server/Locales/en-US.json b/src/BootstrapBlazor.Server/Locales/en-US.json index 04430971451..a730af06088 100644 --- a/src/BootstrapBlazor.Server/Locales/en-US.json +++ b/src/BootstrapBlazor.Server/Locales/en-US.json @@ -6599,6 +6599,7 @@ "Intro": "Dropdown boxes are used to display the selection requirements for complex types for any component", "NormalTitle": "Basic usage", "NormalIntro": "Use built in ListView component to select images", + "NormalDesc": "You can use IsClearable to control whether to display the clear button. The default value is false", "MinWidthTitle": "Min-Width", "MinWidthIntro": "Change the minimum width of the dropdown box by setting the DropdownMinWidth value", "HeightTitle": "Height", diff --git a/src/BootstrapBlazor.Server/Locales/zh-CN.json b/src/BootstrapBlazor.Server/Locales/zh-CN.json index 30571a9b950..bdfc8ab39be 100644 --- a/src/BootstrapBlazor.Server/Locales/zh-CN.json +++ b/src/BootstrapBlazor.Server/Locales/zh-CN.json @@ -6599,6 +6599,7 @@ "Intro": "下拉框为任意组件用于展示复杂类型的选择需求", "NormalTitle": "基本功能", "NormalIntro": "内置 ListView 组件选择图片", + "NormalDesc": "可通过 IsClearable 控制是否显示清除小按钮,默认值 false", "MinWidthTitle": "设置最小宽度", "MinWidthIntro": "通过设置 DropdownMinWidth 值,来改变下拉框最小宽度", "HeightTitle": "设置高度", diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index 0ea84b0f811..908fccc2888 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@ - 9.2.7-beta01 + 9.2.7-beta02 diff --git a/src/BootstrapBlazor/Components/Select/SelectObject.razor b/src/BootstrapBlazor/Components/Select/SelectObject.razor index 06065e56f0a..72aab3f0a28 100644 --- a/src/BootstrapBlazor/Components/Select/SelectObject.razor +++ b/src/BootstrapBlazor/Components/Select/SelectObject.razor @@ -31,6 +31,10 @@ } + @if (GetClearable()) + { + + } diff --git a/src/BootstrapBlazor/Components/Select/SelectObject.razor.cs b/src/BootstrapBlazor/Components/Select/SelectObject.razor.cs index 1bb7ec0fb68..bc6bfbe8691 100644 --- a/src/BootstrapBlazor/Components/Select/SelectObject.razor.cs +++ b/src/BootstrapBlazor/Components/Select/SelectObject.razor.cs @@ -46,6 +46,19 @@ public partial class SelectObject [NotNull] public string? DropdownIcon { get; set; } + /// + /// 获得/设置 是否可清除 默认 false + /// + [Parameter] + public bool IsClearable { get; set; } + + /// + /// 获得/设置 右侧清除图标 默认 fa-solid fa-angle-up + /// + [Parameter] + [NotNull] + public string? ClearIcon { get; set; } + /// /// 获得/设置 下拉列表内容模板 /// @@ -66,6 +79,7 @@ public partial class SelectObject /// private string? ClassName => CssBuilder.Default("select select-object dropdown") .AddClass("disabled", IsDisabled) + .AddClass("cls", IsClearable) .AddClassFromAttributes(AdditionalAttributes) .Build(); @@ -89,6 +103,12 @@ public partial class SelectObject .AddClass($"text-danger", IsValid.HasValue && !IsValid.Value) .Build(); + private string? ClearClassString => CssBuilder.Default("clear-icon") + .AddClass($"text-{Color.ToDescriptionString()}", Color != Color.None) + .AddClass($"text-success", IsValid.HasValue && IsValid.Value) + .AddClass($"text-danger", IsValid.HasValue && !IsValid.Value) + .Build(); + /// /// 获得 PlaceHolder 属性 /// @@ -107,6 +127,12 @@ public partial class SelectObject [Parameter] public RenderFragment? Template { get; set; } + /// + /// 获得/设置 清除文本内容 OnClear 回调方法 默认 null + /// + [Parameter] + public Func? OnClearAsync { get; set; } + [Inject] [NotNull] private IStringLocalizer>? Localizer { get; set; } @@ -151,6 +177,7 @@ protected override void OnParametersSet() PlaceHolder ??= Localizer[nameof(PlaceHolder)]; DropdownIcon ??= IconTheme.GetIconByKey(ComponentIcons.SelectDropdownIcon); + ClearIcon ??= IconTheme.GetIconByKey(ComponentIcons.SelectClearIcon); } /// @@ -159,6 +186,8 @@ protected override void OnParametersSet() /// protected override bool IsRequired() => ValidateForm != null; + private bool GetClearable() => IsClearable && !IsDisabled; + /// /// 获得 Text 显示文字 /// @@ -170,4 +199,15 @@ protected override void OnParametersSet() /// /// public Task CloseAsync() => InvokeVoidAsync("close", Id); + + private async Task OnClearValue() + { + if (OnClearAsync != null) + { + await OnClearAsync(); + } + + Value = default; + await CloseAsync(); + } } diff --git a/test/UnitTest/Components/SelectObjectTest.cs b/test/UnitTest/Components/SelectObjectTest.cs index b90bb029663..583c40c4c7c 100644 --- a/test/UnitTest/Components/SelectObjectTest.cs +++ b/test/UnitTest/Components/SelectObjectTest.cs @@ -48,6 +48,24 @@ public async Task Value_Ok() await cut.InvokeAsync(() => item.Click()); Assert.NotNull(v); Assert.Equal(url, v.ImageUrl); + + var isClear = false; + cut.SetParametersAndRender(pb => + { + pb.Add(a => a.IsClearable, true); + pb.Add(a => a.OnClearAsync, () => + { + isClear = true; + return Task.CompletedTask; + }); + }); + Assert.Contains("clear-icon", cut.Markup); + + var span = cut.Find(".clear-icon"); + await cut.InvokeAsync(() => span.Click()); + var input = cut.Find(".form-select"); + Assert.Null(input.GetAttribute("value")); + Assert.True(isClear); } [Fact] @@ -61,8 +79,12 @@ public void Color_Ok() { pb.AddContent(0, "test"); }); + pb.Add(a => a.IsClearable, true); }); cut.Contains("border-danger"); + + var span = cut.Find(".clear-icon"); + Assert.True(span.ClassList.Contains("text-danger")); } [Fact] @@ -182,6 +204,7 @@ public async Task Validate_Ok() builder.Add(a => a.Model, model); builder.AddChildContent>(pb => { + pb.Add(a => a.IsClearable, true); pb.Add(a => a.Value, model.Name); pb.Add(a => a.ValueExpression, Utility.GenerateValueExpression(model, "Name", typeof(string))); pb.Add(a => a.OnValueChanged, v => @@ -205,6 +228,9 @@ await cut.InvokeAsync(() => }); Assert.True(valid); + var span = cut.Find(".clear-icon"); + Assert.True(span.ClassList.Contains("text-success")); + model.Name = null; var table = cut.FindComponent>(); table.SetParametersAndRender(); @@ -214,6 +240,9 @@ await cut.InvokeAsync(() => form.Submit(); }); Assert.True(invalid); + + span = cut.Find(".clear-icon"); + Assert.True(span.ClassList.Contains("text-danger")); } class Product