diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index a855779081a..46b5ff3f9b1 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@ - 9.2.0-beta03 + 9.2.0-beta01 diff --git a/src/BootstrapBlazor/Components/Input/BootstrapInput.razor b/src/BootstrapBlazor/Components/Input/BootstrapInput.razor index 36b60fc0ac0..8b52c29b4fd 100644 --- a/src/BootstrapBlazor/Components/Input/BootstrapInput.razor +++ b/src/BootstrapBlazor/Components/Input/BootstrapInput.razor @@ -7,4 +7,25 @@ } - +@if (Clearable) +{ +
+ @RenderInput + @if (!IsDisabled && !Readonly) + { + + } +
+} +else +{ + @RenderInput +} + +@code { + RenderFragment RenderInput => + @; +} diff --git a/src/BootstrapBlazor/Components/Input/BootstrapInput.razor.cs b/src/BootstrapBlazor/Components/Input/BootstrapInput.razor.cs index 22d872b6409..9fd621de5e8 100644 --- a/src/BootstrapBlazor/Components/Input/BootstrapInput.razor.cs +++ b/src/BootstrapBlazor/Components/Input/BootstrapInput.razor.cs @@ -22,8 +22,47 @@ public partial class BootstrapInput [Parameter] public bool AutoSetDefaultWhenNull { get; set; } + /// + /// 获得/设置 是否显示清空小按钮 默认 false + /// + [Parameter] + public bool Clearable { get; set; } + + /// + /// 获得/设置 清空文本框时回调方法 默认 null + /// + [Parameter] + public Func? OnClear { get; set; } + + /// + /// 获得/设置 清空小按钮图标 默认 null + /// + [Parameter] + public string? ClearableIcon { get; set; } + + /// + /// 图标主题服务 + /// + [Inject] + [NotNull] + private IIconTheme? IconTheme { get; set; } + private string? ReadonlyString => Readonly ? "true" : null; + private string? ClearableIconString => CssBuilder.Default("form-control-clear-icon") + .AddClass(ClearableIcon) + .Build(); + + /// + /// + /// + protected override void OnParametersSet() + { + base.OnParametersSet(); + + ClearableIcon ??= IconTheme.GetIconByKey(ComponentIcons.InputClearIcon); + } + /// /// /// @@ -47,4 +86,13 @@ protected override bool TryParseValueFromString(string value, [MaybeNullWhen(fal } return ret; } + + private async Task OnClickClear() + { + if (OnClear != null) + { + await OnClear(Value); + } + CurrentValueAsString = ""; + } } diff --git a/src/BootstrapBlazor/Components/Input/BootstrapInput.razor.js b/src/BootstrapBlazor/Components/Input/BootstrapInput.razor.js index 961f4f67dde..08468520762 100644 --- a/src/BootstrapBlazor/Components/Input/BootstrapInput.razor.js +++ b/src/BootstrapBlazor/Components/Input/BootstrapInput.razor.js @@ -11,7 +11,7 @@ export function handleKeyUp(id, invoke, enter, enterCallbackMethod, esc, escCall const el = document.getElementById(id) if (el) { EventHandler.on(el, 'keyup', e => { - if (enter && e.key === 'Enter') { + if (enter && (e.key === 'Enter' || e.key === 'NumpadEnter')) { invoke.invokeMethodAsync(enterCallbackMethod, el.value) } else if (esc && e.key === 'Escape') { diff --git a/src/BootstrapBlazor/Components/Input/BootstrapInput.razor.scss b/src/BootstrapBlazor/Components/Input/BootstrapInput.razor.scss new file mode 100644 index 00000000000..a1ea2c7166d --- /dev/null +++ b/src/BootstrapBlazor/Components/Input/BootstrapInput.razor.scss @@ -0,0 +1,29 @@ +.bb-clearable-input { + display: inline-flex; + align-items: center; + flex-grow: 1; + width: 100%; + position: relative; + + > input { + width: 100%; + flex-grow: 1; + padding-right: 2rem; + + + .form-control-clear-icon { + color: var(--bb-border-hover-color); + cursor: pointer; + position: absolute; + right: 11px; + display: none; + } + + &:focus + .form-control-clear-icon { + display: block; + } + } + + &:hover .form-control-clear-icon { + display: block; + } +} diff --git a/src/BootstrapBlazor/Enums/ComponentIcons.cs b/src/BootstrapBlazor/Enums/ComponentIcons.cs index 355817eab06..266c4d731d1 100644 --- a/src/BootstrapBlazor/Enums/ComponentIcons.cs +++ b/src/BootstrapBlazor/Enums/ComponentIcons.cs @@ -818,5 +818,10 @@ public enum ComponentIcons /// /// ThemeProvider 组件 明亮模式图标 /// - ThemeProviderActiveModeIcon + ThemeProviderActiveModeIcon, + + /// + /// Input 组件 ClearIcon 图标 + /// + InputClearIcon } diff --git a/src/BootstrapBlazor/Options/IconThemeOptions.cs b/src/BootstrapBlazor/Options/IconThemeOptions.cs index 40a47561778..c260c393d2f 100644 --- a/src/BootstrapBlazor/Options/IconThemeOptions.cs +++ b/src/BootstrapBlazor/Options/IconThemeOptions.cs @@ -106,6 +106,8 @@ public IconThemeOptions() { ComponentIcons.ImageViewerFileIcon, "fa-regular fa-file-image" }, + { ComponentIcons.InputClearIcon, "fa-regular fa-circle-xmark" }, + { ComponentIcons.InputNumberMinusIcon, "fa-solid fa-circle-minus" }, { ComponentIcons.InputNumberPlusIcon, "fa-solid fa-circle-plus" }, diff --git a/src/BootstrapBlazor/wwwroot/scss/components.scss b/src/BootstrapBlazor/wwwroot/scss/components.scss index e78752c69d3..e769dd7bc4f 100644 --- a/src/BootstrapBlazor/wwwroot/scss/components.scss +++ b/src/BootstrapBlazor/wwwroot/scss/components.scss @@ -51,6 +51,7 @@ @import "../../Components/IFrame/IFrame.razor.scss"; @import "../../Components/ImagePreviewer/ImagePreviewer.razor.scss"; @import "../../Components/ImageViewer/ImageViewer.razor.scss"; +@import "../../Components/Input/BootstrapInput.razor.scss"; @import "../../Components/Input/BootstrapInputGroup.razor.scss"; @import "../../Components/Input/FloatingLabel.razor.scss"; @import "../../Components/IpAddress/IpAddress.razor.scss"; diff --git a/test/UnitTest/Components/InputTest.cs b/test/UnitTest/Components/InputTest.cs index e3209b4242a..9169033e623 100644 --- a/test/UnitTest/Components/InputTest.cs +++ b/test/UnitTest/Components/InputTest.cs @@ -69,6 +69,42 @@ public void Readonly_Ok() cut.Contains("readonly=\"true\""); } + [Fact] + public void Clearable_Ok() + { + var cut = Context.RenderComponent>(builder => builder.Add(a => a.Clearable, false)); + cut.DoesNotContain("bb-clearable-input"); + + cut.SetParametersAndRender(pb => pb.Add(a => a.Clearable, true)); + cut.Contains("bb-clearable-input"); + cut.Contains("form-control-clear-icon"); + + cut.SetParametersAndRender(pb => pb.Add(a => a.Readonly, true)); + cut.DoesNotContain("form-control-clear-icon"); + + cut.SetParametersAndRender(pb => pb.Add(a => a.Readonly, false)); + cut.SetParametersAndRender(pb => pb.Add(a => a.IsDisabled, true)); + cut.DoesNotContain("form-control-clear-icon"); + } + + [Fact] + public async Task OnClear_Ok() + { + var clicked = false; + var cut = Context.RenderComponent>(builder => + { + builder.Add(a => a.Clearable, true); + builder.Add(a => a.OnClear, v => + { + clicked = true; + return Task.CompletedTask; + }); + }); + var icon = cut.Find(".form-control-clear-icon"); + await cut.InvokeAsync(() => icon.Click()); + Assert.True(clicked); + } + [Fact] public async Task OnInput_Ok() {