Skip to content

Commit f83730d

Browse files
feat(Input): add Clearable parameter (#4991)
* feat: 增加 InputClearIcon 定义 * feat: 增加 Clearable 属性 * refactor: 禁用模式下不显示 Clear 按钮 * test: 增加单元测试 * chore: bump version 9.2.0-beta01 Co-Authored-By: zph19970424 <51937136+zph19970424@users.noreply.github.com> --------- Co-authored-by: zph19970424 <51937136+zph19970424@users.noreply.github.com>
1 parent 70ebace commit f83730d

File tree

9 files changed

+146
-4
lines changed

9 files changed

+146
-4
lines changed

src/BootstrapBlazor/BootstrapBlazor.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk.Razor">
22

33
<PropertyGroup>
4-
<Version>9.2.0-beta03</Version>
4+
<Version>9.2.0-beta01</Version>
55
</PropertyGroup>
66

77
<ItemGroup>

src/BootstrapBlazor/Components/Input/BootstrapInput.razor

+22-1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,25 @@
77
<BootstrapLabel required="@Required" for="@Id" ShowLabelTooltip="ShowLabelTooltip" Value="@DisplayText" />
88
}
99

10-
<input @attributes="@AdditionalAttributes" type="@Type" placeholder="@PlaceHolder" id="@Id" readonly="@ReadonlyString" class="@ClassName" disabled="@Disabled" @bind-value="CurrentValueAsString" @bind-value:event="@EventString" @onblur="@OnBlur" @ref="FocusElement" />
10+
@if (Clearable)
11+
{
12+
<div class="bb-clearable-input">
13+
@RenderInput
14+
@if (!IsDisabled && !Readonly)
15+
{
16+
<i class="@ClearableIconString" @onclick="OnClickClear"></i>
17+
}
18+
</div>
19+
}
20+
else
21+
{
22+
@RenderInput
23+
}
24+
25+
@code {
26+
RenderFragment RenderInput =>
27+
@<input @attributes="@AdditionalAttributes" type="@Type" id="@Id" class="@ClassName"
28+
readonly="@ReadonlyString" disabled="@Disabled"
29+
placeholder="@PlaceHolder"
30+
@bind-value="CurrentValueAsString" @bind-value:event="@EventString" @onblur="@OnBlur" @ref="FocusElement" />;
31+
}

src/BootstrapBlazor/Components/Input/BootstrapInput.razor.cs

+48
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,47 @@ public partial class BootstrapInput<TValue>
2222
[Parameter]
2323
public bool AutoSetDefaultWhenNull { get; set; }
2424

25+
/// <summary>
26+
/// 获得/设置 是否显示清空小按钮 默认 false
27+
/// </summary>
28+
[Parameter]
29+
public bool Clearable { get; set; }
30+
31+
/// <summary>
32+
/// 获得/设置 清空文本框时回调方法 默认 null
33+
/// </summary>
34+
[Parameter]
35+
public Func<TValue, Task>? OnClear { get; set; }
36+
37+
/// <summary>
38+
/// 获得/设置 清空小按钮图标 默认 null
39+
/// </summary>
40+
[Parameter]
41+
public string? ClearableIcon { get; set; }
42+
43+
/// <summary>
44+
/// 图标主题服务
45+
/// </summary>
46+
[Inject]
47+
[NotNull]
48+
private IIconTheme? IconTheme { get; set; }
49+
2550
private string? ReadonlyString => Readonly ? "true" : null;
2651

52+
private string? ClearableIconString => CssBuilder.Default("form-control-clear-icon")
53+
.AddClass(ClearableIcon)
54+
.Build();
55+
56+
/// <summary>
57+
/// <inheritdoc/>
58+
/// </summary>
59+
protected override void OnParametersSet()
60+
{
61+
base.OnParametersSet();
62+
63+
ClearableIcon ??= IconTheme.GetIconByKey(ComponentIcons.InputClearIcon);
64+
}
65+
2766
/// <summary>
2867
/// <inheritdoc/>
2968
/// </summary>
@@ -47,4 +86,13 @@ protected override bool TryParseValueFromString(string value, [MaybeNullWhen(fal
4786
}
4887
return ret;
4988
}
89+
90+
private async Task OnClickClear()
91+
{
92+
if (OnClear != null)
93+
{
94+
await OnClear(Value);
95+
}
96+
CurrentValueAsString = "";
97+
}
5098
}

src/BootstrapBlazor/Components/Input/BootstrapInput.razor.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export function handleKeyUp(id, invoke, enter, enterCallbackMethod, esc, escCall
1111
const el = document.getElementById(id)
1212
if (el) {
1313
EventHandler.on(el, 'keyup', e => {
14-
if (enter && e.key === 'Enter') {
14+
if (enter && (e.key === 'Enter' || e.key === 'NumpadEnter')) {
1515
invoke.invokeMethodAsync(enterCallbackMethod, el.value)
1616
}
1717
else if (esc && e.key === 'Escape') {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
.bb-clearable-input {
2+
display: inline-flex;
3+
align-items: center;
4+
flex-grow: 1;
5+
width: 100%;
6+
position: relative;
7+
8+
> input {
9+
width: 100%;
10+
flex-grow: 1;
11+
padding-right: 2rem;
12+
13+
+ .form-control-clear-icon {
14+
color: var(--bb-border-hover-color);
15+
cursor: pointer;
16+
position: absolute;
17+
right: 11px;
18+
display: none;
19+
}
20+
21+
&:focus + .form-control-clear-icon {
22+
display: block;
23+
}
24+
}
25+
26+
&:hover .form-control-clear-icon {
27+
display: block;
28+
}
29+
}

src/BootstrapBlazor/Enums/ComponentIcons.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -818,5 +818,10 @@ public enum ComponentIcons
818818
/// <summary>
819819
/// ThemeProvider 组件 明亮模式图标
820820
/// </summary>
821-
ThemeProviderActiveModeIcon
821+
ThemeProviderActiveModeIcon,
822+
823+
/// <summary>
824+
/// Input 组件 ClearIcon 图标
825+
/// </summary>
826+
InputClearIcon
822827
}

src/BootstrapBlazor/Options/IconThemeOptions.cs

+2
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ public IconThemeOptions()
106106

107107
{ ComponentIcons.ImageViewerFileIcon, "fa-regular fa-file-image" },
108108

109+
{ ComponentIcons.InputClearIcon, "fa-regular fa-circle-xmark" },
110+
109111
{ ComponentIcons.InputNumberMinusIcon, "fa-solid fa-circle-minus" },
110112
{ ComponentIcons.InputNumberPlusIcon, "fa-solid fa-circle-plus" },
111113

src/BootstrapBlazor/wwwroot/scss/components.scss

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
@import "../../Components/IFrame/IFrame.razor.scss";
5252
@import "../../Components/ImagePreviewer/ImagePreviewer.razor.scss";
5353
@import "../../Components/ImageViewer/ImageViewer.razor.scss";
54+
@import "../../Components/Input/BootstrapInput.razor.scss";
5455
@import "../../Components/Input/BootstrapInputGroup.razor.scss";
5556
@import "../../Components/Input/FloatingLabel.razor.scss";
5657
@import "../../Components/IpAddress/IpAddress.razor.scss";

test/UnitTest/Components/InputTest.cs

+36
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,42 @@ public void Readonly_Ok()
6969
cut.Contains("readonly=\"true\"");
7070
}
7171

72+
[Fact]
73+
public void Clearable_Ok()
74+
{
75+
var cut = Context.RenderComponent<BootstrapInput<string>>(builder => builder.Add(a => a.Clearable, false));
76+
cut.DoesNotContain("bb-clearable-input");
77+
78+
cut.SetParametersAndRender(pb => pb.Add(a => a.Clearable, true));
79+
cut.Contains("bb-clearable-input");
80+
cut.Contains("form-control-clear-icon");
81+
82+
cut.SetParametersAndRender(pb => pb.Add(a => a.Readonly, true));
83+
cut.DoesNotContain("form-control-clear-icon");
84+
85+
cut.SetParametersAndRender(pb => pb.Add(a => a.Readonly, false));
86+
cut.SetParametersAndRender(pb => pb.Add(a => a.IsDisabled, true));
87+
cut.DoesNotContain("form-control-clear-icon");
88+
}
89+
90+
[Fact]
91+
public async Task OnClear_Ok()
92+
{
93+
var clicked = false;
94+
var cut = Context.RenderComponent<BootstrapInput<string>>(builder =>
95+
{
96+
builder.Add(a => a.Clearable, true);
97+
builder.Add(a => a.OnClear, v =>
98+
{
99+
clicked = true;
100+
return Task.CompletedTask;
101+
});
102+
});
103+
var icon = cut.Find(".form-control-clear-icon");
104+
await cut.InvokeAsync(() => icon.Click());
105+
Assert.True(clicked);
106+
}
107+
72108
[Fact]
73109
public async Task OnInput_Ok()
74110
{

0 commit comments

Comments
 (0)