feat(module: input): add ChangeOnInput, modify the default binding behavior to bind value in the onchange event (#2838)

* feat(module: input): add ChangeOnInput, modify the default binding behavior to bind in the onchange event

* fix can't change immediately when debounce milliseconds is 0

* add tests
This commit is contained in:
James Yeung 2022-10-26 22:41:13 +08:00 committed by GitHub
parent 47c07b858f
commit 2a59463f8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 128 additions and 35 deletions

View File

@ -76,12 +76,26 @@ namespace AntDesign
[Parameter]
public bool Bordered { get; set; } = true;
/// <summary>
/// Whether to change value on input
/// </summary>
[Parameter]
public bool ChangeOnInput { get; set; }
/// <summary>
/// Delays the processing of the KeyUp event until the user has stopped
/// typing for a predetermined amount of time
/// </summary>
[Parameter]
public int DebounceMilliseconds { get; set; } = 250;
public int DebounceMilliseconds
{
get => _debounceMilliseconds;
set
{
_debounceMilliseconds = value;
ChangeOnInput = value >= 0;
}
}
/// <summary>
/// The initial input content
@ -186,7 +200,6 @@ namespace AntDesign
[Parameter]
public RenderFragment Suffix { get; set; }
/// <summary>
/// The type of input, see: MDN(use `Input.TextArea` instead of type=`textarea`)
/// </summary>
@ -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

View File

@ -0,0 +1,18 @@
<div>
onchange:
<Input Placeholder="onblur change" @bind-Value="@txtValue" />
<br />
<br />
oninput:
<Input Placeholder="oninput change" @bind-Value="@txtValue" ChangeOnInput />
<br />
<br />
oninput without debounce:
<Input Placeholder="oninput change" @bind-Value="@txtValue" DebounceMilliseconds="0" />
<br />
<br />
<Text>@txtValue</Text>
</div>
@code{
string txtValue { get; set; }
}

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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<AntDesign.Input<string>>(@<AntDesign.Input @bind-Value=@value />);
//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<AntDesign.Input<string>>(@<AntDesign.Input @bind-Value=@value DebounceMilliseconds="@debounce" />);
//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<AntDesign.Input<string>>(@<AntDesign.Input @bind-Value=@value ChangeOnInput />);
//Act
cut.Find("input").Input("newValue");
cut.Find("input").KeyUp(String.Empty);
//Assert
await Task.Delay(250);
cut.Instance.Value.Should().Be("newValue");
}
}