diff --git a/src/BootstrapBlazor.Server/Components/Samples/Selects.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/Selects.razor.cs index 4c2eb39bbdf..342b2117f46 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/Selects.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/Selects.razor.cs @@ -321,6 +321,14 @@ private AttributeItem[] GetAttributes() => DefaultValue = "false" }, new() + { + Name = "IsAutoClearSearchTextWhenCollapsed", + Description = Localizer["SelectsIsAutoClearSearchTextWhenCollapsed"], + Type = "bool", + ValueList = "true|false", + DefaultValue = "false" + }, + new() { Name = "DisplayText", Description = Localizer["SelectsDisplayText"], diff --git a/src/BootstrapBlazor.Server/Locales/en-US.json b/src/BootstrapBlazor.Server/Locales/en-US.json index c0a3000adce..04a5dd1a7cc 100644 --- a/src/BootstrapBlazor.Server/Locales/en-US.json +++ b/src/BootstrapBlazor.Server/Locales/en-US.json @@ -3211,7 +3211,7 @@ "SelectsCustomTemplateTitle": "Custom option templates", "SelectsCustomTemplateIntro": "By setting the ItemTemplate you can customize the option rendering style", "SelectsShowSearchTitle": "Drop-down box with search box", - "SelectsShowSearchIntro": "Controls whether the search box is displayed by setting the ShowSearch property, which is not displayed by default false", + "SelectsShowSearchIntro": "Controls whether the search box is displayed by setting the ShowSearch property, which is not displayed by default false. You can set the IsAutoClearSearchTextWhenCollapsed parameter to control whether the text in the search box is automatically cleared after the drop-down box is collapsed. The default value is false.", "SelectsConfirmSelectTitle": "Drop-down box with confirmation", "SelectsConfirmSelectIntro": "Block changes to the current value by setting the OnBeforeSelectedItemChange delegate.", "SelectsTimeZoneTitle": "Timezone", @@ -3221,6 +3221,7 @@ "SwalFooter": "Click Confirm to change the option value and select Cancel to leave the value unchanged", "SelectsShowLabel": "Whether to display the front label", "SelectsShowSearch": "Whether to display the search box", + "SelectsIsAutoClearSearchTextWhenCollapsed": "Whether to automatically clear the search bar when the drop-down box is collapsed", "SelectsDisplayText": "The front label displays text", "SelectsClass": "Style", "SelectsColor": "Color", diff --git a/src/BootstrapBlazor.Server/Locales/zh-CN.json b/src/BootstrapBlazor.Server/Locales/zh-CN.json index d32ba236160..c5478c69f93 100644 --- a/src/BootstrapBlazor.Server/Locales/zh-CN.json +++ b/src/BootstrapBlazor.Server/Locales/zh-CN.json @@ -3211,7 +3211,7 @@ "SelectsCustomTemplateTitle": "自定义选项模板", "SelectsCustomTemplateIntro": "通过设置 ItemTemplate 可以自定义选项渲染样式", "SelectsShowSearchTitle": "带搜索框的下拉框", - "SelectsShowSearchIntro": "通过设置 ShowSearch 属性控制是否显示搜索框,默认为 false 不显示搜索框", + "SelectsShowSearchIntro": "通过设置 ShowSearch 属性控制是否显示搜索框,默认为 false 不显示搜索框,可以通过设置 IsAutoClearSearchTextWhenCollapsed 参数控制下拉框收起后是否自动清空搜索框内文字,默认值为 false 不清空", "SelectsConfirmSelectTitle": "带确认的下拉框", "SelectsConfirmSelectIntro": "通过设置 OnBeforeSelectedItemChange 委托,阻止当前值的改变", "SelectsTimeZoneTitle": "时区下拉框", @@ -3221,6 +3221,7 @@ "SwalFooter": "点击确认改变选项值,选择取消后值不变", "SelectsShowLabel": "是否显示前置标签", "SelectsShowSearch": "是否显示搜索框", + "SelectsIsAutoClearSearchTextWhenCollapsed": "下拉框收起时是否自动清空搜索栏", "SelectsDisplayText": "前置标签显示文本", "SelectsClass": "样式", "SelectsColor": "颜色", diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index 44b52dd7b2e..1ba7569b506 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@  - 9.6.2-beta05 + 9.6.2-beta06 diff --git a/src/BootstrapBlazor/Components/Select/Select.razor.cs b/src/BootstrapBlazor/Components/Select/Select.razor.cs index ace96cecc2e..4f6f2434056 100644 --- a/src/BootstrapBlazor/Components/Select/Select.razor.cs +++ b/src/BootstrapBlazor/Components/Select/Select.razor.cs @@ -18,26 +18,13 @@ public partial class Select : ISelect, ILookup [NotNull] private SwalService? SwalService { get; set; } - private string? ClassString => CssBuilder.Default("select dropdown") - .AddClass("is-clearable", IsClearable) - .AddClassFromAttributes(AdditionalAttributes) - .Build(); - - private string? InputClassString => CssBuilder.Default("form-select form-control") - .AddClass($"border-{Color.ToDescriptionString()}", Color != Color.None && !IsDisabled && !IsValid.HasValue) - .AddClass($"border-success", IsValid.HasValue && IsValid.Value) - .AddClass($"border-danger", IsValid.HasValue && !IsValid.Value) - .AddClass(CssClass).AddClass(ValidCss) - .Build(); - - private string? ActiveItem(SelectedItem item) => CssBuilder.Default("dropdown-item") - .AddClass("active", item.Value == CurrentValueAsString) - .AddClass("disabled", item.IsDisabled) - .Build(); - - private readonly List _children = []; + [Inject] + [NotNull] + private IStringLocalizer>? Localizer { get; set; } - private string? ScrollIntoViewBehaviorString => ScrollIntoViewBehavior == ScrollIntoViewBehavior.Smooth ? null : ScrollIntoViewBehavior.ToDescriptionString(); + [Inject] + [NotNull] + private ILookupService? InjectLookupService { get; set; } /// /// Gets or sets the display template. Default is null. @@ -125,31 +112,47 @@ public partial class Select : ISelect, ILookup public string? DefaultVirtualizeItemText { get; set; } /// - /// + /// Gets or sets whether auto clear the search text when dropdown closed. /// - IEnumerable? ILookup.Lookup { get; set; } + [Parameter] + public bool IsAutoClearSearchTextWhenCollapsed { get; set; } /// - /// + /// Gets or sets the dropdown collapsed callback method. /// - StringComparison ILookup.LookupStringComparison { get; set; } + [Parameter] + public Func? OnCollapsed { get; set; } - [Inject] - [NotNull] - private IStringLocalizer>? Localizer { get; set; } + IEnumerable? ILookup.Lookup { get => Items; set => Items = value; } - /// - /// Gets or sets the injected lookup service instance. - /// - [Inject] - [NotNull] - private ILookupService? InjectLookupService { get; set; } + StringComparison ILookup.LookupStringComparison { get => StringComparison; set => StringComparison = value; } /// /// /// protected override string? RetrieveId() => InputId; + private string? ClassString => CssBuilder.Default("select dropdown") + .AddClass("is-clearable", IsClearable) + .AddClassFromAttributes(AdditionalAttributes) + .Build(); + + private string? InputClassString => CssBuilder.Default("form-select form-control") + .AddClass($"border-{Color.ToDescriptionString()}", Color != Color.None && !IsDisabled && !IsValid.HasValue) + .AddClass($"border-success", IsValid.HasValue && IsValid.Value) + .AddClass($"border-danger", IsValid.HasValue && !IsValid.Value) + .AddClass(CssClass).AddClass(ValidCss) + .Build(); + + private string? ActiveItem(SelectedItem item) => CssBuilder.Default("dropdown-item") + .AddClass("active", item.Value == CurrentValueAsString) + .AddClass("disabled", item.IsDisabled) + .Build(); + + private readonly List _children = []; + + private string? ScrollIntoViewBehaviorString => ScrollIntoViewBehavior == ScrollIntoViewBehavior.Smooth ? null : ScrollIntoViewBehavior.ToDescriptionString(); + private string? InputId => $"{Id}_input"; private bool _init = true; @@ -169,44 +172,6 @@ private SelectedItem? SelectedRow } } - private SelectedItem? GetSelectedRow() - { - if (Value is null) - { - _lastSelectedValueString = ""; - _init = false; - return null; - } - - var item = IsVirtualize ? GetItemByVirtualized() : GetItemByRows(); - if (item != null) - { - if (_init && DisableItemChangedWhenFirstRender) - { - - } - else - { - _ = SelectedItemChanged(item); - _init = false; - } - } - return item; - } - - private SelectedItem? GetItemWithEnumValue() => ValueType.IsEnum ? Rows.Find(i => i.Value == Convert.ToInt32(Value).ToString()) : null; - - private SelectedItem GetItemByVirtualized() => new(CurrentValueAsString, _defaultVirtualizedItemText); - - private SelectedItem? GetItemByRows() - { - var item = GetItemWithEnumValue() - ?? Rows.Find(i => i.Value == CurrentValueAsString) - ?? Rows.Find(i => i.Active) - ?? Rows.FirstOrDefault(i => !i.IsDisabled); - return item; - } - /// /// /// @@ -308,9 +273,30 @@ private bool TryParseSelectItem(string value, [MaybeNullWhen(false)] out TValue protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, new { ConfirmMethodCallback = nameof(ConfirmSelectedItem), - SearchMethodCallback = nameof(TriggerOnSearch) + SearchMethodCallback = nameof(TriggerOnSearch), + TriggerCollapsed = (OnCollapsed != null || IsAutoClearSearchTextWhenCollapsed) ? nameof(TriggerCollapsed) : null }); + /// + /// Trigger event callback method. called by JavaScript. + /// + /// + [JSInvokable] + public async Task TriggerCollapsed() + { + if (OnCollapsed != null) + { + await OnCollapsed(); + } + + if (IsAutoClearSearchTextWhenCollapsed) + { + SearchText = string.Empty; + _itemsCache = null; + StateHasChanged(); + } + } + /// /// /// @@ -440,4 +426,42 @@ private async Task OnChange(ChangeEventArgs args) } } } + + private SelectedItem? GetSelectedRow() + { + if (Value is null) + { + _lastSelectedValueString = ""; + _init = false; + return null; + } + + var item = IsVirtualize ? GetItemByVirtualized() : GetItemByRows(); + if (item != null) + { + if (_init && DisableItemChangedWhenFirstRender) + { + + } + else + { + _ = SelectedItemChanged(item); + _init = false; + } + } + return item; + } + + private SelectedItem? GetItemWithEnumValue() => ValueType.IsEnum ? Rows.Find(i => i.Value == Convert.ToInt32(Value).ToString()) : null; + + private SelectedItem GetItemByVirtualized() => new(CurrentValueAsString, _defaultVirtualizedItemText); + + private SelectedItem? GetItemByRows() + { + var item = GetItemWithEnumValue() + ?? Rows.Find(i => i.Value == CurrentValueAsString) + ?? Rows.Find(i => i.Active) + ?? Rows.FirstOrDefault(i => !i.IsDisabled); + return item; + } } diff --git a/src/BootstrapBlazor/Components/Select/Select.razor.js b/src/BootstrapBlazor/Components/Select/Select.razor.js index 3731aafcb19..af61156b497 100644 --- a/src/BootstrapBlazor/Components/Select/Select.razor.js +++ b/src/BootstrapBlazor/Components/Select/Select.razor.js @@ -10,7 +10,13 @@ export function init(id, invoke, options) { } const search = el.querySelector(".search-text") - const popover = Popover.init(el) + const popover = Popover.init(el, { + hideCallback: () => { + if (options.triggerCollapsed) { + invoke.invokeMethodAsync(options.triggerCollapsed); + } + } + }); const input = el.querySelector(`#${id}_input`); const select = { el, invoke, options, diff --git a/test/UnitTest/Components/SelectTest.cs b/test/UnitTest/Components/SelectTest.cs index 180d5f63dd7..5cd20702bb6 100644 --- a/test/UnitTest/Components/SelectTest.cs +++ b/test/UnitTest/Components/SelectTest.cs @@ -428,6 +428,34 @@ public void Color_Ok() Assert.Contains("border-danger", cut.Markup); } + [Fact] + public async Task OnCollapsed_Ok() + { + var collapsed = false; + var cut = Context.RenderComponent>(pb => + { + pb.Add(a => a.OnCollapsed, () => { collapsed = true; return Task.CompletedTask; }); + }); + await cut.InvokeAsync(() => cut.Instance.TriggerCollapsed()); + Assert.True(collapsed); + } + + [Fact] + public async Task IsAutoClearSearchTextWhenCollapsed_Ok() + { + var cut = Context.RenderComponent>(pb => + { + pb.Add(a => a.ShowSearch, true); + pb.Add(a => a.IsAutoClearSearchTextWhenCollapsed, true); + }); + + await cut.InvokeAsync(() => cut.Instance.TriggerOnSearch("123")); + cut.Contains("value=\"123\""); + + await cut.InvokeAsync(() => cut.Instance.TriggerCollapsed()); + cut.Contains("value=\"\""); + } + [Fact] public void Validate_Ok() {