diff --git a/components/input/Input.cs b/components/input/Input.cs index 7fbc521e..01570ba3 100644 --- a/components/input/Input.cs +++ b/components/input/Input.cs @@ -46,7 +46,7 @@ namespace AntDesign public RenderFragment AddOnAfter { get; set; } /// - /// Allow to remove input content with clear icon + /// Allow to remove input content with clear icon /// [Parameter] public bool AllowClear { get; set; } @@ -77,14 +77,28 @@ namespace AntDesign public bool Bordered { get; set; } = true; /// - /// Delays the processing of the KeyUp event until the user has stopped + /// Whether to change value on input + /// + [Parameter] + public bool ChangeOnInput { get; set; } + + /// + /// Delays the processing of the KeyUp event until the user has stopped /// typing for a predetermined amount of time /// [Parameter] - public int DebounceMilliseconds { get; set; } = 250; + public int DebounceMilliseconds + { + get => _debounceMilliseconds; + set + { + _debounceMilliseconds = value; + ChangeOnInput = value >= 0; + } + } /// - /// The initial input content + /// The initial input content /// [Parameter] public TValue DefaultValue { get; set; } @@ -186,7 +200,6 @@ namespace AntDesign [Parameter] public RenderFragment Suffix { get; set; } - /// /// The type of input, see: MDN(use `Input.TextArea` instead of type=`textarea`) /// @@ -194,10 +207,10 @@ namespace AntDesign public string Type { get; set; } = "text"; /// - /// Set CSS style of wrapper. Is used when component has visible: Prefix/Suffix + /// Set CSS style of wrapper. Is used when component has visible: Prefix/Suffix /// or has paramter set or for components: - /// and . In these cases, html span elements is used - /// to wrap the html input element. + /// and . In these cases, html span elements is used + /// to wrap the html input element. /// is used on the span element. /// [Parameter] @@ -228,7 +241,7 @@ namespace AntDesign /// /// Removes focus from input element. - /// + /// public async Task Blur() { await BlurAsync(Ref); @@ -251,8 +264,7 @@ namespace AntDesign private Timer _debounceTimer; private bool _autoFocus; private bool _isInitialized; - - private bool DebounceEnabled => DebounceMilliseconds != 0; + private int _debounceMilliseconds = 250; protected bool IsFocused { get; set; } @@ -335,23 +347,17 @@ namespace AntDesign SetClasses(); } - protected virtual async Task OnChangeAsync(ChangeEventArgs args) + protected virtual Task OnChangeAsync(ChangeEventArgs args) { - if (CurrentValueAsString != args?.Value?.ToString()) - { - CurrentValueAsString = args?.Value?.ToString(); - if (OnChange.HasDelegate) - { - await OnChange.InvokeAsync(Value); - } - } + ChangeValue(true); + return Task.CompletedTask; } protected async Task OnKeyPressAsync(KeyboardEventArgs args) { if (args?.Key == "Enter" && InputType != "textarea") { - await ChangeValue(true); + ChangeValue(true); if (EnableOnPressEnter) { await OnPressEnter.InvokeAsync(args); @@ -364,7 +370,7 @@ namespace AntDesign protected async Task OnKeyUpAsync(KeyboardEventArgs args) { - await ChangeValue(); + ChangeValue(); if (OnkeyUp.HasDelegate) await OnkeyUp.InvokeAsync(args); } @@ -376,7 +382,7 @@ namespace AntDesign protected async Task OnMouseUpAsync(MouseEventArgs args) { - await ChangeValue(true); + ChangeValue(true); if (OnMouseUp.HasDelegate) await OnMouseUp.InvokeAsync(args); } @@ -391,8 +397,6 @@ namespace AntDesign _compositionInputting = false; } - await ChangeValue(true); - if (OnBlur.HasDelegate) { await OnBlur.InvokeAsync(e); @@ -455,14 +459,14 @@ namespace AntDesign protected void DebounceTimerIntervalOnTick(object state) { - InvokeAsync(async () => await ChangeValue(true)); + InvokeAsync(() => ChangeValue(true)); } - private async Task ChangeValue(bool ignoreDebounce = false) + protected void ChangeValue(bool force = false) { - if (DebounceEnabled) + if (ChangeOnInput) { - if (!ignoreDebounce) + if (_debounceMilliseconds > 0 && !force) { DebounceChangeValue(); return; @@ -470,11 +474,14 @@ namespace AntDesign if (_debounceTimer != null) { - await _debounceTimer.DisposeAsync(); - + _debounceTimer.Dispose(); _debounceTimer = null; } } + else if (!force) + { + return; + } if (!_compositionInputting) { @@ -483,7 +490,7 @@ namespace AntDesign CurrentValue = _inputValue; if (OnChange.HasDelegate) { - await OnChange.InvokeAsync(Value); + OnChange.InvokeAsync(Value); } } } @@ -653,6 +660,7 @@ namespace AntDesign builder.AddAttribute(72, "onkeypress", CallbackFactory.Create(this, OnKeyPressAsync)); builder.AddAttribute(73, "onkeydown", CallbackFactory.Create(this, OnkeyDownAsync)); builder.AddAttribute(74, "onkeyup", CallbackFactory.Create(this, OnKeyUpAsync)); + builder.AddAttribute(75, "oninput", CallbackFactory.Create(this, OnInputAsync)); //TODO: Use built in @onfocus once https://github.com/dotnet/aspnetcore/issues/30070 is solved diff --git a/site/AntDesign.Docs/Demos/Components/Input/demo/ChangeOnInput.razor b/site/AntDesign.Docs/Demos/Components/Input/demo/ChangeOnInput.razor new file mode 100644 index 00000000..38aa810b --- /dev/null +++ b/site/AntDesign.Docs/Demos/Components/Input/demo/ChangeOnInput.razor @@ -0,0 +1,18 @@ +
+ onchange: + +
+
+ oninput: + +
+
+ oninput without debounce: + +
+
+ @txtValue +
+@code{ + string txtValue { get; set; } +} diff --git a/site/AntDesign.Docs/Demos/Components/Input/demo/change-on-input.md b/site/AntDesign.Docs/Demos/Components/Input/demo/change-on-input.md new file mode 100644 index 00000000..e2132d5b --- /dev/null +++ b/site/AntDesign.Docs/Demos/Components/Input/demo/change-on-input.md @@ -0,0 +1,19 @@ +--- +order: 0 +title: + zh-CN: 输入时变更 + en-US: Change on input +--- + +## zh-CN + +从 0.13.0 开始,输入框的值绑定事件默认变为 `onchange`,如果需要在输入时绑定,请设置 `ChangeOnInput`。 + +> 注意:当在 Server Side 使用输入时变更,会存在绑定延迟和文本回退问题,可尝试加大 `DebounceMilliseconds` 的值来解决。[#579](https://github.com/ant-design-blazor/ant-design-blazor/issues/579) + + +## en-US + +Starting from 0.13.0, the input box value binding event defaults to 'onchange'. If you need to bind at input time, set 'ChangeOnInput'. + +> Note: When using input changes on the Server Side, there are binding delays and text rollback issues, you can try to address them by increasing the values of `DebounceMilliseconds`. [#579](https://github.com/ant-design-blazor/ant-design-blazor/issues/579) \ No newline at end of file diff --git a/site/AntDesign.Docs/Demos/Components/Input/doc/index.en-US.md b/site/AntDesign.Docs/Demos/Components/Input/doc/index.en-US.md index 39cd0f6e..85a27509 100644 --- a/site/AntDesign.Docs/Demos/Components/Input/doc/index.en-US.md +++ b/site/AntDesign.Docs/Demos/Components/Input/doc/index.en-US.md @@ -25,8 +25,9 @@ A basic widget for getting the user input is a text field. Keyboard and mouse ca | AllowComplete | Controls the autocomplete attribute of the input HTML element. | boolean | true | | AutoFocus | Focus on input element. | boolean | false | Bordered | Whether has border style | boolean | true +| ChangeOnInput | Whether to change on input | boolean | false | | CultureInfo | What Culture will be used when converting string to value and value to string. Useful for InputNumber component. | CultureInfo | CultureInfo.CurrentCulture | -| DebounceMilliseconds | Delays the processing of the KeyUp event until the user has stopped typing for a predetermined amount of time | int | 250 | +| DebounceMilliseconds | Delays the processing of the KeyUp event until the user has stopped typing for a predetermined amount of time. The setting will enable 'ChangeOnInput'. | int | 250 | | DefaultValue | The initial input content | TValue | - | | Disabled | Whether the input is disabled. | boolean | false | | InputElementSuffixClass |Css class that will be added to input element class as the last class entry. | string | - | 0.9 diff --git a/site/AntDesign.Docs/Demos/Components/Input/doc/index.zh-CN.md b/site/AntDesign.Docs/Demos/Components/Input/doc/index.zh-CN.md index 6b8cb6d5..711c9941 100644 --- a/site/AntDesign.Docs/Demos/Components/Input/doc/index.zh-CN.md +++ b/site/AntDesign.Docs/Demos/Components/Input/doc/index.zh-CN.md @@ -16,6 +16,8 @@ cover: https://gw.alipayobjects.com/zos/alicdn/xS9YEJhfe/Input.svg ## API +### Common API + | 参数 | 说明 | 类型 | 默认值 | | ---------------- | -------------------------------------------- | ------------- | --------- | | AddOnBefore | 带标签的 input,设置前置标签 | RenderFragment | - | @@ -24,8 +26,9 @@ cover: https://gw.alipayobjects.com/zos/alicdn/xS9YEJhfe/Input.svg | AllowComplete | 控制 Input HTML 元素的自动完成属性. | boolean | true | | AutoFocus | 自动聚焦. | boolean | false | Bordered | 是否有边框 | boolean | true +| ChangeOnInput | 是否输入时变更 | boolean | false | | CultureInfo | 文本格式化时区域本地化选项 | CultureInfo | CultureInfo.CurrentCulture | -| DebounceMilliseconds | 延迟 KeyUp 事件的处理,直到用户停止输入一段预定的时间 | int | 250 | +| DebounceMilliseconds | 延迟 KeyUp 事件的处理,直到用户停止输入一段预定的时间,设置后会开启 `ChangeOnInput` | int | 250 | | DefaultValue |输入框默认内容 | TValue | - | | Disabled |是否禁用状态,默认为 false | boolean | false | | InputElementSuffixClass |作为最后一个Css Class 添加到Input的类目中. | string | - | 0.9 diff --git a/tests/AntDesign.Tests/Input/InputTests.razor b/tests/AntDesign.Tests/Input/InputTests.razor index 20953cc7..7987bf5f 100644 --- a/tests/AntDesign.Tests/Input/InputTests.razor +++ b/tests/AntDesign.Tests/Input/InputTests.razor @@ -122,6 +122,19 @@ cut.Find("span.ant-input-clear-icon").GetAttribute("style").Should().Contain("visibility: hidden"); } + [Fact] + public void Input_default_change() + { + //Arrange + string? value = null; + var cut = Render>(@); + //Act + cut.Find("input").Input("newValue"); // get value on input + cut.Find("input").Change("newValue"); // binding value on change + //Assert + cut.Instance.Value.Should().Be("newValue"); + } + [Fact] public async Task Input_Debounce_sets_value() { @@ -133,8 +146,39 @@ cut.Find("input").Input("newValue"); cut.Find("input").KeyUp(String.Empty); //Assert - await Task.Delay(debounce + 100); + await Task.Delay(debounce); cut.Instance.Value.Should().Be("newValue"); } + + [Fact] + public void Input_Debounce_sets_zero() + { + //Arrange + string? value = null; + int debounce = 0; + var cut = Render>(@); + //Act + cut.Find("input").Input("newValue"); + cut.Find("input").KeyUp(String.Empty); + //Assert + + cut.Instance.Value.Should().Be("newValue"); + } + + [Fact] + public async Task Input_with_changeOnInput() + { + //Arrange + string? value = null; + + var cut = Render>(@); + //Act + cut.Find("input").Input("newValue"); + cut.Find("input").KeyUp(String.Empty); + //Assert + await Task.Delay(250); + + cut.Instance.Value.Should().Be("newValue"); + } } \ No newline at end of file