Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
45180f7
feat(BootstrapInputNumber): 组件或配置中设置了Step的HTML标准属性导致表单提交时引导HTML的标准校验
Tony-ST0754 Jun 5, 2026
02268ac
feat(BootstrapInputNumber): 组件或配置中设置了Step的HTML标准属性导致表单提交时引导HTML的标准校验
Tony-ST0754 Jun 5, 2026
6dcba07
feat(BootstrapInputNumber): 组件或配置中设置了Step的HTML标准属性导致表单提交时引导HTML的标准校验
Tony-ST0754 Jun 5, 2026
e8eadec
更新 InputNumberTest.cs
Tony-ST0754 Jun 5, 2026
7699e9a
Merge branch 'main' into fix-InputNumber-Step-Standard
ArgoZhang Jun 6, 2026
942b6e3
refactor: 移除 ParseDecimal 方法使用原生方法
ArgoZhang Jun 6, 2026
e04c10c
refactor: 移除 static 关键字
ArgoZhang Jun 6, 2026
9e044a1
refactor: 重构代码
ArgoZhang Jun 6, 2026
124efec
refactor: 重构 Index
ArgoZhang Jun 6, 2026
8d7a1ea
test: 消除警告信息
ArgoZhang Jun 6, 2026
8838638
test: 消除警告信息
ArgoZhang Jun 6, 2026
2b85742
refactor: 重构代码提高可读性
ArgoZhang Jun 6, 2026
3e3a611
test: 更新单元测试
ArgoZhang Jun 6, 2026
6be2fb2
docs:更新InputNumber组件文档
Tony-ST0754 Jun 6, 2026
bd5f7ed
revert: 撤销四舍五入内置逻辑
ArgoZhang Jun 6, 2026
8af3b06
test: 撤销单元测试
ArgoZhang Jun 6, 2026
4ccdda4
revert: 撤销 IsDecimalType 判断方法
ArgoZhang Jun 6, 2026
7b5c3b0
Revert "docs:更新InputNumber组件文档"
ArgoZhang Jun 6, 2026
79fd37a
test: 补全 StepSettings 设置
ArgoZhang Jun 6, 2026
9b88e74
test: 更新单元测试
ArgoZhang Jun 6, 2026
236fb4b
feat: 增加 Step 默认值规则允许小数点的使用 any 其他使用 null
ArgoZhang Jun 6, 2026
6cd62a5
refactor: 重构 GetStepString 方法支持 细化 any 逻辑
ArgoZhang Jun 6, 2026
75f309d
test: 更新单元测试
ArgoZhang Jun 6, 2026
d537039
refactor: 更新逻辑
ArgoZhang Jun 6, 2026
79f9016
test: 更新单元测试
ArgoZhang Jun 6, 2026
53aa819
doc: 调整顺序
ArgoZhang Jun 6, 2026
63c5ace
refactor: 调整数据类型接收 any 设置
ArgoZhang Jun 6, 2026
c836639
test: 更新单元测试
ArgoZhang Jun 6, 2026
bd7dbdd
chore: bump version 10.7.1
ArgoZhang Jun 6, 2026
ecafac6
doc: 增加 Step 文档
ArgoZhang Jun 6, 2026
8b68938
doc: 支持多语言
ArgoZhang Jun 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 38 additions & 13 deletions src/BootstrapBlazor.Server/Components/Samples/InputNumbers.razor
Original file line number Diff line number Diff line change
Expand Up @@ -72,19 +72,44 @@
<DemoBlock Title="@Localizer["InputNumbersStepTitle"]"
Introduction="@Localizer["InputNumbersStepIntro"]"
Name="Step">
<div>
<div class="demo-input-number">
<p>@Localizer["InputNumbersStep1"]</p>
<BootstrapInputNumber ShowButton="true" Value="5" Color="Color.Danger" />
</div>
<div class="demo-input-number mt-3">
<p>@Localizer["InputNumbersStep10"]</p>
<BootstrapInputNumber ShowButton="true" Value="10" Step="10" Color="Color.Success" />
</div>
<div class="demo-input-number mt-3">
<p>@Localizer["InputNumbersStep0.1"]</p>
<BootstrapInputNumber ShowButton="true" Value="0.5" Step="0.1" Formatter="@Formatter" Color="Color.Warning" />
</div>
<section ignore>
<p>@((MarkupString)Localizer["InputNumbersStepDocLink"].Value)</p>
<p>@((MarkupString)Localizer["InputNumbersStepLegacyConfigDescription"].Value)</p>
<Pre>{
"BootstrapBlazorOptions": {
"StepSettings": {
"Float": "0.01",
"Double": "0.01",
"Decimal": "0.01"
}
}</Pre>
<p>@((MarkupString)Localizer["InputNumbersStepCurrentConfigDescription"].Value)</p>
<Pre>{
"BootstrapBlazorOptions": {
"StepSettings": {
"Float": "any",
"Double": "any",
"Decimal": "any"
}
}</Pre>
<p>@((MarkupString)Localizer["InputNumbersStepOptionsDescription"].Value)</p>
<Pre>[Inject]
[NotNull]
private IOptions&lt;BootstrapBlazorOptions&gt;? BootstrapBlazorOptions { get; set; }</Pre>
<p class="code-label">@Localizer["InputNumbersStepNotesTitle"]</p>
<p>@((MarkupString)Localizer["InputNumbersStepNotesDescription"].Value)</p>
</section>
<div class="demo-input-number">
<p>@Localizer["InputNumbersStep1"]</p>
<BootstrapInputNumber ShowButton="true" Value="5" Color="Color.Danger" />
</div>
<div class="demo-input-number mt-3">
<p>@Localizer["InputNumbersStep10"]</p>
<BootstrapInputNumber ShowButton="true" Value="10" Step="10" Color="Color.Success" />
</div>
<div class="demo-input-number mt-3">
<p>@Localizer["InputNumbersStep0.1"]</p>
<BootstrapInputNumber ShowButton="true" Value="0.5" Step="0.1" Formatter="@Formatter" Color="Color.Warning" />
</div>
</DemoBlock>

Expand Down
8 changes: 7 additions & 1 deletion src/BootstrapBlazor.Server/Locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -2767,7 +2767,7 @@
"InputNumbersColorDescription2": "No button",
"InputNumbersColorIntro": "Set the <code>Color</code> parameter to customize the button color",
"InputNumbersColorTitle": "Color",
"InputNumbersDateTypeIntro": "This component uses generic support the underlying data type <code>int short long float double decimal</code>",
"InputNumbersDateTypeIntro": "This component uses generics to support the underlying data types <code>sbyte byte short ushort int uint long ulong float double decimal</code>",
"InputNumbersDateTypeTitle": "Data type",
"InputNumbersDescription": "Only standard numeric values are allowed, and custom ranges and other advanced features are supported",
"InputNumbersDisabledIntro": "When you set the <code>IsDisabled</code> property value to <code>true</code>, the component suppresses input",
Expand All @@ -2784,7 +2784,13 @@
"InputNumbersStep0.1": "The step defaults to 0.1",
"InputNumbersStep1": "The step defaults to 1",
"InputNumbersStep10": "The step defaults to 10",
"InputNumbersStepCurrentConfigDescription": "Starting with version <code>10.7.1</code>, the default configuration was adjusted. The default values for <code>float</code>, <code>double</code>, and <code>decimal</code> changed from <code>0.01</code> to <code>any</code>, and all other numeric types changed from <code>1</code> to <code>null</code> (unset).",
"InputNumbersStepDocLink": "For the related <code>html</code> <code>step</code> documentation, see <a href='https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/number?wt.mc_id=DT-MVP-5004174#step' target='_blank'>the docs</a>",
"InputNumbersStepIntro": "Set the <code>Step</code> parameter to control the increase or decrease in steps",
"InputNumbersStepLegacyConfigDescription": "Before version <code>10.7.1</code>, the project template configured the default <code>step</code> values in <code>appsettings.json</code> like this:",
"InputNumbersStepNotesDescription": "When <code>Step</code>, <code>Min</code>, and <code>Max</code> are set, the browser will apply the native <code>html</code> validation rules if the component value does not match the configured <code>step</code> strategy, and you need to handle that behavior yourself.",
"InputNumbersStepNotesTitle": "Notes",
"InputNumbersStepOptionsDescription": "You can also configure the default <code>step</code> value by injecting <code>BootstrapBlazorOptions</code> and setting its <code>StepSettings</code>.",
"InputNumbersStepTitle": "Custom steps",
"InputNumbersTitle": "InputNumber",
"InputNumbersUseInputEventIntro": "Component uses <code>OnInput</code> event for value updates",
Expand Down
8 changes: 7 additions & 1 deletion src/BootstrapBlazor.Server/Locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -2767,7 +2767,7 @@
"InputNumbersColorDescription2": "无按钮",
"InputNumbersColorIntro": "设置 <code>Color</code> 参数来自定义按钮颜色",
"InputNumbersColorTitle": "颜色",
"InputNumbersDateTypeIntro": "本组件采用泛型支持 <code>int short long float double decimal</code> 基础数据类型",
"InputNumbersDateTypeIntro": "本组件采用泛型支持 <code>sbyte byte short ushort int uint long ulong float double decimal</code> 基础数据类型",
"InputNumbersDateTypeTitle": "数据类型",
"InputNumbersDescription": "仅允许输入标准的数字值,支持自定义范围及其他高级功能",
"InputNumbersDisabledIntro": "设置 <code>IsDisabled</code> 属性值为 <code>true</code> 时,组件禁止输入",
Expand All @@ -2784,7 +2784,13 @@
"InputNumbersStep0.1": "步长设置为 0.1",
"InputNumbersStep1": "步长默认为 1",
"InputNumbersStep10": "步长设置为 10",
"InputNumbersStepCurrentConfigDescription": "<code>10.7.1</code> 版本后默认配置做了调整,<code>float</code> <code>double</code> <code>decimal</code> 默认值由 <code>0.01</code> 改为 <code>any</code>,其他数据类型由 <code>1</code> 更改为 <code>null</code> 未设置。",
"InputNumbersStepDocLink": "<code>html</code> 相关 <code>step</code> 文档请查阅 <a href='https://developer.mozilla.org/zh-CN/docs/Web/HTML/Reference/Elements/input/number?wt.mc_id=DT-MVP-5004174#step' target='_blank'>传送门</a>",
"InputNumbersStepIntro": "设置 <code>Step</code> 参数来控制增加或减少的步长",
"InputNumbersStepLegacyConfigDescription": "<code>10.7.1</code> 版本前项目模板文件通过 <code>appsettings.json</code> 配置文件设置了数据默认 <code>step</code> 配置如下:",
"InputNumbersStepNotesDescription": "设置 <code>Step</code> <code>Min</code> <code>Max</code> 值时,当组件值不符合 <code>step</code> 策略时会触发 <code>html</code> 验证规则,需要自行处理。",
"InputNumbersStepNotesTitle": "特别说明",
"InputNumbersStepOptionsDescription": "也可以通过注入服务 <code>BootstrapBlazorOptions</code> 对象的 <code>StepSettings</code> 来配置默认的 <code>step</code> 值。",
"InputNumbersStepTitle": "自定义步长",
"InputNumbersTitle": "InputNumber 组件",
"InputNumbersUseInputEventIntro": "组件使用 OnInput 事件进行数值更新",
Expand Down
2 changes: 1 addition & 1 deletion src/BootstrapBlazor/BootstrapBlazor.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<Version>10.7.0</Version>
<Version>10.7.1</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ protected override void OnParametersSet()
MinusIcon ??= IconTheme.GetIconByKey(ComponentIcons.InputNumberMinusIcon);
PlusIcon ??= IconTheme.GetIconByKey(ComponentIcons.InputNumberPlusIcon);

StepString = Step ?? StepOption.CurrentValue.GetStep<TValue>() ?? "any";
StepString = Step ?? StepOption.CurrentValue.GetStep<TValue>() ?? GetStepStringByType();

if (Value is null)
{
Expand Down Expand Up @@ -189,9 +189,17 @@ protected override void OnAfterRender(bool firstRender)
_ => throw new InvalidOperationException($"Unsupported type {value!.GetType()}")
};

private string GetStepString() => (string.IsNullOrEmpty(StepString) || StepString.Equals("any", StringComparison.OrdinalIgnoreCase)) ? "1" : StepString;
private string GetStepString()
{
if (string.IsNullOrEmpty(StepString)
|| StepString.Equals("null", StringComparison.OrdinalIgnoreCase)
|| StepString.Equals("any", StringComparison.OrdinalIgnoreCase))
{
return "1";
}

private static decimal ParseDecimal(string value) => decimal.Parse(value, CultureInfo.InvariantCulture);
return StepString;
}

private static TValue ParseValue(string value)
{
Expand All @@ -200,6 +208,15 @@ private static TValue ParseValue(string value)
: throw new InvalidOperationException($"Unsupported type {typeof(TValue)}");
}

private string GetStepStringByType() => IsDecimalType() ? "any" : "null";

private bool IsDecimalType()
{
// 检查是否允许带小数点数据类型
var type = NullableUnderlyingType ?? typeof(TValue);
return type == typeof(float) || type == typeof(double) || type == typeof(decimal);
}

private static TValue? Calculate(TValue? value, string step, bool increment)
{
TValue? ret = default;
Expand All @@ -208,17 +225,17 @@ private static TValue ParseValue(string value)
var factor = increment ? 1 : -1;
ret = value switch
{
sbyte @sbyte => (TValue)(object)(sbyte)Math.Clamp(@sbyte + factor * ParseDecimal(step), sbyte.MinValue, sbyte.MaxValue),
byte @byte => (TValue)(object)(byte)Math.Clamp(@byte + factor * ParseDecimal(step), byte.MinValue, byte.MaxValue),
short @short => (TValue)(object)(short)Math.Clamp(@short + factor * ParseDecimal(step), short.MinValue, short.MaxValue),
ushort @ushort => (TValue)(object)(ushort)Math.Clamp(@ushort + factor * ParseDecimal(step), ushort.MinValue, ushort.MaxValue),
int @int => (TValue)(object)(int)Math.Clamp(@int + factor * ParseDecimal(step), int.MinValue, int.MaxValue),
uint @uint => (TValue)(object)(uint)Math.Clamp(@uint + factor * ParseDecimal(step), uint.MinValue, uint.MaxValue),
long @long => (TValue)(object)(long)Math.Clamp(@long + factor * ParseDecimal(step), long.MinValue, long.MaxValue),
ulong @ulong => (TValue)(object)(ulong)Math.Clamp(@ulong + factor * ParseDecimal(step), ulong.MinValue, ulong.MaxValue),
float @float => (TValue)(object)(@float + factor * float.Parse(step, CultureInfo.InvariantCulture)),
double @double => (TValue)(object)(@double + factor * double.Parse(step, CultureInfo.InvariantCulture)),
decimal @decimal => (TValue)(object)(@decimal + factor * ParseDecimal(step)),
sbyte @sbyte => (TValue)(object)(sbyte)Math.Clamp(@sbyte + factor * Convert.ToDecimal(step), sbyte.MinValue, sbyte.MaxValue),
byte @byte => (TValue)(object)(byte)Math.Clamp(@byte + factor * Convert.ToDecimal(step), byte.MinValue, byte.MaxValue),
short @short => (TValue)(object)(short)Math.Clamp(@short + factor * Convert.ToDecimal(step), short.MinValue, short.MaxValue),
ushort @ushort => (TValue)(object)(ushort)Math.Clamp(@ushort + factor * Convert.ToDecimal(step), ushort.MinValue, ushort.MaxValue),
int @int => (TValue)(object)(int)Math.Clamp(@int + factor * Convert.ToDecimal(step), int.MinValue, int.MaxValue),
uint @uint => (TValue)(object)(uint)Math.Clamp(@uint + factor * Convert.ToDecimal(step), uint.MinValue, uint.MaxValue),
long @long => (TValue)(object)(long)Math.Clamp(@long + factor * Convert.ToDecimal(step), long.MinValue, long.MaxValue),
ulong @ulong => (TValue)(object)(ulong)Math.Clamp(@ulong + factor * Convert.ToDecimal(step), ulong.MinValue, ulong.MaxValue),
float @float => (TValue)(object)(float)Math.Clamp(@float + factor * Convert.ToSingle(step), float.MinValue, float.MaxValue),
double @double => (TValue)(object)(double)Math.Clamp(@double + factor * Convert.ToDouble(step), double.MinValue, double.MaxValue),
decimal @decimal => (TValue)(object)(decimal)Math.Clamp(@decimal + factor * Convert.ToDecimal(step), decimal.MinValue, decimal.MaxValue),
_ => value
};
}
Expand Down
86 changes: 68 additions & 18 deletions src/BootstrapBlazor/Options/StepSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,41 +11,71 @@ namespace BootstrapBlazor.Components;
/// </summary>
public class StepSettings
{
/// <summary>
/// <para lang="zh">获得/设置 sbyte 数据类型步长 默认 null 未设置</para>
/// <para lang="en">Gets or sets sbyte type step default null</para>
/// </summary>
public string? SByte { get; set; }

/// <summary>
/// <para lang="zh">获得/设置 byte 数据类型步长 默认 null 未设置</para>
/// <para lang="en">Gets or sets byte type step default null</para>
/// </summary>
public string? Byte { get; set; }

/// <summary>
/// <para lang="zh">获得/设置 uint 数据类型步长 默认 null 未设置</para>
/// <para lang="en">Gets or sets uint type step default null</para>
/// </summary>
public string? UInt { get; set; }

/// <summary>
/// <para lang="zh">获得/设置 int 数据类型步长 默认 null 未设置</para>
/// <para lang="en">Gets or sets int type step default null</para>
/// </summary>
public int? Int { get; set; }
public string? Int { get; set; }

/// <summary>
/// <para lang="zh">获得/设置 long 数据类型步长 默认 null 未设置</para>
/// <para lang="en">Gets or sets long type step default null</para>
/// </summary>
public int? Long { get; set; }
public string? Long { get; set; }

/// <summary>
/// <para lang="zh">获得/设置 ulong 数据类型步长 默认 null 未设置</para>
/// <para lang="en">Gets or sets ulong type step default null</para>
/// </summary>
public string? ULong { get; set; }

/// <summary>
/// <para lang="zh">获得/设置 short 数据类型步长 默认 null 未设置</para>
/// <para lang="en">Gets or sets short type step default null</para>
/// </summary>
public int? Short { get; set; }
public string? Short { get; set; }

/// <summary>
/// <para lang="zh">获得/设置 ushort 数据类型步长 默认 null 未设置</para>
/// <para lang="en">Gets or sets ushort type step default null</para>
/// </summary>
public string? UShort { get; set; }

/// <summary>
/// <para lang="zh">获得/设置 float 数据类型步长 默认 null 未设置</para>
/// <para lang="en">Gets or sets float type step default null</para>
/// </summary>
public float? Float { get; set; }
public string? Float { get; set; }

/// <summary>
/// <para lang="zh">获得/设置 double 数据类型步长 默认 null 未设置</para>
/// <para lang="en">Gets or sets double type step default null</para>
/// </summary>
public double? Double { get; set; }
public string? Double { get; set; }

/// <summary>
/// <para lang="zh">获得/设置 decimal 数据类型步长 默认 null 未设置</para>
/// <para lang="en">Gets or sets decimal type step default null</para>
/// </summary>
public decimal? Decimal { get; set; }
public string? Decimal { get; set; }

/// <summary>
/// <para lang="zh">获得步长字符串</para>
Expand All @@ -55,29 +85,49 @@ public class StepSettings
public string? GetStep(Type type)
{
string? ret = null;
if (type == typeof(int))
if (type == typeof(sbyte))
{
ret = SByte;
}
else if (type == typeof(byte))
{
ret = Byte;
}
else if (type == typeof(uint))
{
ret = UInt;
}
else if (type == typeof(int))
{
ret = Int;
}
else if (type == typeof(long))
{
ret = Long;
}
else if (type == typeof(short))
{
ret = Int?.ToString();
ret = Short;
}
if (type == typeof(long))
else if (type == typeof(ushort))
{
ret = Long?.ToString();
ret = UShort;
}
if (type == typeof(short))
else if (type == typeof(ulong))
{
ret = Short?.ToString();
ret = ULong;
}
if (type == typeof(float))
else if (type == typeof(float))
{
ret = Float?.ToString();
ret = Float;
}
if (type == typeof(double))
else if (type == typeof(double))
{
ret = Double?.ToString();
ret = Double;
}
if (type == typeof(decimal))
else if (type == typeof(decimal))
{
ret = Decimal?.ToString();
ret = Decimal;
}
return ret;
}
Expand Down
35 changes: 19 additions & 16 deletions test/UnitTest/Components/InputNumberTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,24 +197,27 @@ public async Task ShowButton_Ok()
}

[Theory]
[InlineData(typeof(sbyte))]
[InlineData(typeof(byte))]
[InlineData(typeof(short))]
[InlineData(typeof(ushort))]
[InlineData(typeof(int))]
[InlineData(typeof(uint))]
[InlineData(typeof(long))]
[InlineData(typeof(ulong))]
[InlineData(typeof(float))]
[InlineData(typeof(double))]
[InlineData(typeof(decimal))]
public async Task Type_Ok(Type t)
[InlineData(typeof(sbyte), null)]
[InlineData(typeof(byte), null)]
[InlineData(typeof(short), null)]
[InlineData(typeof(ushort), null)]
[InlineData(typeof(int), null)]
[InlineData(typeof(uint), null)]
[InlineData(typeof(long), null)]
[InlineData(typeof(ulong), null)]
[InlineData(typeof(float), null)]
[InlineData(typeof(double), null)]
[InlineData(typeof(decimal), null)]
[InlineData(typeof(int), "any")]
[InlineData(typeof(float), "any")]
public async Task Type_Ok(Type t, string? step)
{
var cut = Context.Render(builder =>
{
builder.OpenComponent(0, typeof(BootstrapInputNumber<>).MakeGenericType(t));
builder.AddAttribute(1, "ShowButton", true);
builder.AddAttribute(1, "Max", "10");
builder.AddAttribute(2, "Max", "10");
builder.AddAttribute(3, "Step", step);
builder.CloseComponent();
});
var buttons = cut.FindAll("button");
Expand Down Expand Up @@ -367,11 +370,11 @@ public void PrivateFallback_Ok()
value = calculateMethod.Invoke(null, [null, "1", true]);
Assert.Null(value);

var cut = new BootstrapInputNumber<int>() { Min = "test" };
var cut = Context.Render<BootstrapInputNumber<int>>(pb => { pb.Add(a => a.Min, "test"); });
var setMinMethod = typeof(BootstrapInputNumber<int>).GetMethod("SetMin", BindingFlags.NonPublic | BindingFlags.Instance);
Assert.NotNull(setMinMethod);

var ex = Assert.Throws<TargetInvocationException>(() => setMinMethod.Invoke(cut, [1]));
var ex = Assert.Throws<TargetInvocationException>(() => setMinMethod.Invoke(cut.Instance, [1]));
Assert.IsType<InvalidOperationException>(ex.InnerException);
}

Expand Down Expand Up @@ -478,7 +481,7 @@ private void AssertInputNumberValueChanged<TValue>(TValue value, string inputVal
var cut = Context.Render<BootstrapInputNumber<TValue>>(pb =>
{
pb.Add(a => a.Value, value);
pb.Add(a => a.ValueChanged, EventCallback.Factory.Create<TValue>(this, v => currentValue = v));
pb.Add(a => a.ValueChanged, EventCallback.Factory.Create<TValue?>(this, v => currentValue = v));
});

var input = cut.Find(".form-control");
Expand Down
Loading
Loading