diff --git a/src/BootstrapBlazor.Server/Components/Samples/InputNumbers.razor b/src/BootstrapBlazor.Server/Components/Samples/InputNumbers.razor index c6f992e8e74..d7e0a5be2e1 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/InputNumbers.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/InputNumbers.razor @@ -72,19 +72,44 @@ -
-
-

@Localizer["InputNumbersStep1"]

- -
-
-

@Localizer["InputNumbersStep10"]

- -
-
-

@Localizer["InputNumbersStep0.1"]

- -
+
+

@((MarkupString)Localizer["InputNumbersStepDocLink"].Value)

+

@((MarkupString)Localizer["InputNumbersStepLegacyConfigDescription"].Value)

+
{
+  "BootstrapBlazorOptions": {
+    "StepSettings": {
+      "Float": "0.01",
+      "Double": "0.01",
+      "Decimal": "0.01"
+    }
+}
+

@((MarkupString)Localizer["InputNumbersStepCurrentConfigDescription"].Value)

+
{
+  "BootstrapBlazorOptions": {
+    "StepSettings": {
+      "Float": "any",
+      "Double": "any",
+      "Decimal": "any"
+    }
+}
+

@((MarkupString)Localizer["InputNumbersStepOptionsDescription"].Value)

+
[Inject]
+[NotNull]
+private IOptions<BootstrapBlazorOptions>? BootstrapBlazorOptions { get; set; }
+

@Localizer["InputNumbersStepNotesTitle"]

+

@((MarkupString)Localizer["InputNumbersStepNotesDescription"].Value)

+
+
+

@Localizer["InputNumbersStep1"]

+ +
+
+

@Localizer["InputNumbersStep10"]

+ +
+
+

@Localizer["InputNumbersStep0.1"]

+
diff --git a/src/BootstrapBlazor.Server/Locales/en-US.json b/src/BootstrapBlazor.Server/Locales/en-US.json index a2d81fffe1f..ec58b31f563 100644 --- a/src/BootstrapBlazor.Server/Locales/en-US.json +++ b/src/BootstrapBlazor.Server/Locales/en-US.json @@ -2767,7 +2767,7 @@ "InputNumbersColorDescription2": "No button", "InputNumbersColorIntro": "Set the Color parameter to customize the button color", "InputNumbersColorTitle": "Color", - "InputNumbersDateTypeIntro": "This component uses generic support the underlying data type int short long float double decimal", + "InputNumbersDateTypeIntro": "This component uses generics to support the underlying data types sbyte byte short ushort int uint long ulong float double decimal", "InputNumbersDateTypeTitle": "Data type", "InputNumbersDescription": "Only standard numeric values are allowed, and custom ranges and other advanced features are supported", "InputNumbersDisabledIntro": "When you set the IsDisabled property value to true, the component suppresses input", @@ -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 10.7.1, the default configuration was adjusted. The default values for float, double, and decimal changed from 0.01 to any, and all other numeric types changed from 1 to null (unset).", + "InputNumbersStepDocLink": "For the related html step documentation, see the docs", "InputNumbersStepIntro": "Set the Step parameter to control the increase or decrease in steps", + "InputNumbersStepLegacyConfigDescription": "Before version 10.7.1, the project template configured the default step values in appsettings.json like this:", + "InputNumbersStepNotesDescription": "When Step, Min, and Max are set, the browser will apply the native html validation rules if the component value does not match the configured step strategy, and you need to handle that behavior yourself.", + "InputNumbersStepNotesTitle": "Notes", + "InputNumbersStepOptionsDescription": "You can also configure the default step value by injecting BootstrapBlazorOptions and setting its StepSettings.", "InputNumbersStepTitle": "Custom steps", "InputNumbersTitle": "InputNumber", "InputNumbersUseInputEventIntro": "Component uses OnInput event for value updates", diff --git a/src/BootstrapBlazor.Server/Locales/zh-CN.json b/src/BootstrapBlazor.Server/Locales/zh-CN.json index 8495be1a2e2..c47993258b0 100644 --- a/src/BootstrapBlazor.Server/Locales/zh-CN.json +++ b/src/BootstrapBlazor.Server/Locales/zh-CN.json @@ -2767,7 +2767,7 @@ "InputNumbersColorDescription2": "无按钮", "InputNumbersColorIntro": "设置 Color 参数来自定义按钮颜色", "InputNumbersColorTitle": "颜色", - "InputNumbersDateTypeIntro": "本组件采用泛型支持 int short long float double decimal 基础数据类型", + "InputNumbersDateTypeIntro": "本组件采用泛型支持 sbyte byte short ushort int uint long ulong float double decimal 基础数据类型", "InputNumbersDateTypeTitle": "数据类型", "InputNumbersDescription": "仅允许输入标准的数字值,支持自定义范围及其他高级功能", "InputNumbersDisabledIntro": "设置 IsDisabled 属性值为 true 时,组件禁止输入", @@ -2784,7 +2784,13 @@ "InputNumbersStep0.1": "步长设置为 0.1", "InputNumbersStep1": "步长默认为 1", "InputNumbersStep10": "步长设置为 10", + "InputNumbersStepCurrentConfigDescription": "10.7.1 版本后默认配置做了调整,float double decimal 默认值由 0.01 改为 any,其他数据类型由 1 更改为 null 未设置。", + "InputNumbersStepDocLink": "html 相关 step 文档请查阅 传送门", "InputNumbersStepIntro": "设置 Step 参数来控制增加或减少的步长", + "InputNumbersStepLegacyConfigDescription": "10.7.1 版本前项目模板文件通过 appsettings.json 配置文件设置了数据默认 step 配置如下:", + "InputNumbersStepNotesDescription": "设置 Step Min Max 值时,当组件值不符合 step 策略时会触发 html 验证规则,需要自行处理。", + "InputNumbersStepNotesTitle": "特别说明", + "InputNumbersStepOptionsDescription": "也可以通过注入服务 BootstrapBlazorOptions 对象的 StepSettings 来配置默认的 step 值。", "InputNumbersStepTitle": "自定义步长", "InputNumbersTitle": "InputNumber 组件", "InputNumbersUseInputEventIntro": "组件使用 OnInput 事件进行数值更新", diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index 3271a09ae5c..e0a3198dd60 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@  - 10.7.0 + 10.7.1 diff --git a/src/BootstrapBlazor/Components/InputNumber/BootstrapInputNumber.razor.cs b/src/BootstrapBlazor/Components/InputNumber/BootstrapInputNumber.razor.cs index 768a46b96f3..7247bc67347 100644 --- a/src/BootstrapBlazor/Components/InputNumber/BootstrapInputNumber.razor.cs +++ b/src/BootstrapBlazor/Components/InputNumber/BootstrapInputNumber.razor.cs @@ -133,7 +133,7 @@ protected override void OnParametersSet() MinusIcon ??= IconTheme.GetIconByKey(ComponentIcons.InputNumberMinusIcon); PlusIcon ??= IconTheme.GetIconByKey(ComponentIcons.InputNumberPlusIcon); - StepString = Step ?? StepOption.CurrentValue.GetStep() ?? "any"; + StepString = Step ?? StepOption.CurrentValue.GetStep() ?? GetStepStringByType(); if (Value is null) { @@ -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) { @@ -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; @@ -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 }; } diff --git a/src/BootstrapBlazor/Options/StepSettings.cs b/src/BootstrapBlazor/Options/StepSettings.cs index 6e1d079da6a..9487e21f9a5 100644 --- a/src/BootstrapBlazor/Options/StepSettings.cs +++ b/src/BootstrapBlazor/Options/StepSettings.cs @@ -11,41 +11,71 @@ namespace BootstrapBlazor.Components; /// public class StepSettings { + /// + /// 获得/设置 sbyte 数据类型步长 默认 null 未设置 + /// Gets or sets sbyte type step default null + /// + public string? SByte { get; set; } + + /// + /// 获得/设置 byte 数据类型步长 默认 null 未设置 + /// Gets or sets byte type step default null + /// + public string? Byte { get; set; } + + /// + /// 获得/设置 uint 数据类型步长 默认 null 未设置 + /// Gets or sets uint type step default null + /// + public string? UInt { get; set; } + /// /// 获得/设置 int 数据类型步长 默认 null 未设置 /// Gets or sets int type step default null /// - public int? Int { get; set; } + public string? Int { get; set; } /// /// 获得/设置 long 数据类型步长 默认 null 未设置 /// Gets or sets long type step default null /// - public int? Long { get; set; } + public string? Long { get; set; } + + /// + /// 获得/设置 ulong 数据类型步长 默认 null 未设置 + /// Gets or sets ulong type step default null + /// + public string? ULong { get; set; } /// /// 获得/设置 short 数据类型步长 默认 null 未设置 /// Gets or sets short type step default null /// - public int? Short { get; set; } + public string? Short { get; set; } + + /// + /// 获得/设置 ushort 数据类型步长 默认 null 未设置 + /// Gets or sets ushort type step default null + /// + public string? UShort { get; set; } /// /// 获得/设置 float 数据类型步长 默认 null 未设置 /// Gets or sets float type step default null /// - public float? Float { get; set; } + public string? Float { get; set; } /// /// 获得/设置 double 数据类型步长 默认 null 未设置 /// Gets or sets double type step default null /// - public double? Double { get; set; } + public string? Double { get; set; } /// /// 获得/设置 decimal 数据类型步长 默认 null 未设置 /// Gets or sets decimal type step default null /// - public decimal? Decimal { get; set; } + public string? Decimal { get; set; } /// /// 获得步长字符串 @@ -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; } diff --git a/test/UnitTest/Components/InputNumberTest.cs b/test/UnitTest/Components/InputNumberTest.cs index feecc2470ae..99b16776498 100644 --- a/test/UnitTest/Components/InputNumberTest.cs +++ b/test/UnitTest/Components/InputNumberTest.cs @@ -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"); @@ -367,11 +370,11 @@ public void PrivateFallback_Ok() value = calculateMethod.Invoke(null, [null, "1", true]); Assert.Null(value); - var cut = new BootstrapInputNumber() { Min = "test" }; + var cut = Context.Render>(pb => { pb.Add(a => a.Min, "test"); }); var setMinMethod = typeof(BootstrapInputNumber).GetMethod("SetMin", BindingFlags.NonPublic | BindingFlags.Instance); Assert.NotNull(setMinMethod); - var ex = Assert.Throws(() => setMinMethod.Invoke(cut, [1])); + var ex = Assert.Throws(() => setMinMethod.Invoke(cut.Instance, [1])); Assert.IsType(ex.InnerException); } @@ -478,7 +481,7 @@ private void AssertInputNumberValueChanged(TValue value, string inputVal var cut = Context.Render>(pb => { pb.Add(a => a.Value, value); - pb.Add(a => a.ValueChanged, EventCallback.Factory.Create(this, v => currentValue = v)); + pb.Add(a => a.ValueChanged, EventCallback.Factory.Create(this, v => currentValue = v)); }); var input = cut.Find(".form-control"); diff --git a/test/UnitTest/Options/BootstrapBlazorOptionsTest.cs b/test/UnitTest/Options/BootstrapBlazorOptionsTest.cs index 48a184b5f02..251de41f2d0 100644 --- a/test/UnitTest/Options/BootstrapBlazorOptionsTest.cs +++ b/test/UnitTest/Options/BootstrapBlazorOptionsTest.cs @@ -60,27 +60,50 @@ public void Options_StepSettings() options.StepSettings = new(); + Assert.Null(options.GetStep()); + Assert.Null(options.GetStep()); Assert.Null(options.GetStep()); + Assert.Null(options.GetStep()); Assert.Null(options.GetStep()); + Assert.Null(options.GetStep()); Assert.Null(options.GetStep()); + Assert.Null(options.GetStep()); Assert.Null(options.GetStep()); Assert.Null(options.GetStep()); Assert.Null(options.GetStep()); - options.StepSettings.Short = 1; - options.StepSettings.Int = 2; - options.StepSettings.Long = 3; - options.StepSettings.Float = 0.1f; - options.StepSettings.Double = 0.01d; - options.StepSettings.Decimal = 0.001M; - + options.StepSettings.SByte = "1"; + options.StepSettings.Byte = "2"; + options.StepSettings.Short = "1"; + options.StepSettings.UShort = "2"; + options.StepSettings.Int = "2"; + options.StepSettings.UInt = "3"; + options.StepSettings.Long = "3"; + options.StepSettings.ULong = "4"; + options.StepSettings.Float = "0.1"; + options.StepSettings.Double = "0.01"; + options.StepSettings.Decimal = "0.001"; + + Assert.Equal("1", options.GetStep()); + Assert.Equal("2", options.GetStep()); Assert.Equal("1", options.GetStep()); + Assert.Equal("2", options.GetStep()); Assert.Equal("2", options.GetStep()); + Assert.Equal("3", options.GetStep()); Assert.Equal("3", options.GetStep(typeof(long?))); + Assert.Equal("4", options.GetStep()); Assert.Equal("0.1", options.GetStep()); Assert.Equal("0.01", options.GetStep()); Assert.Equal("0.001", options.GetStep()); + + options.StepSettings.Float = "any"; + options.StepSettings.Double = "any"; + options.StepSettings.Decimal = "any"; + + Assert.Equal("any", options.GetStep()); + Assert.Equal("any", options.GetStep()); + Assert.Equal("any", options.GetStep()); } [Fact]