From 94a478102f991241f28e26f762400dbc449d6e76 Mon Sep 17 00:00:00 2001 From: Benjamin Vertonghen Date: Thu, 30 Jan 2025 13:26:34 +0100 Subject: [PATCH 01/16] Format : Float --- src/Components/Components/src/BindConverter.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Components/Components/src/BindConverter.cs b/src/Components/Components/src/BindConverter.cs index 06a03a05f390..a59d49f4b800 100644 --- a/src/Components/Components/src/BindConverter.cs +++ b/src/Components/Components/src/BindConverter.cs @@ -212,10 +212,15 @@ private static string FormatShortValueCore(short value, CultureInfo? culture) /// /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static string FormatValue(float value, CultureInfo? culture = null) => FormatFloatValueCore(value, culture); + public static string FormatValue(float value, CultureInfo? culture = null, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format = null) => FormatFloatValueCore(value, culture, format); - private static string FormatFloatValueCore(float value, CultureInfo? culture) + private static string FormatFloatValueCore(float value, CultureInfo? culture, string? format) { + if (format != null) + { + return value.ToString(format, culture ?? CultureInfo.CurrentCulture); + } + return value.ToString(culture ?? CultureInfo.CurrentCulture); } @@ -228,15 +233,20 @@ private static string FormatFloatValueCore(float value, CultureInfo? culture) /// /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static string? FormatValue(float? value, CultureInfo? culture = null) => FormatNullableFloatValueCore(value, culture); + public static string? FormatValue(float? value, CultureInfo? culture = null, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format = null) => FormatNullableFloatValueCore(value, culture, format); - private static string? FormatNullableFloatValueCore(float? value, CultureInfo? culture) + private static string? FormatNullableFloatValueCore(float? value, CultureInfo? culture, string? format) { if (value == null) { return null; } + if (format != null) + { + return value.ToString(format, culture ?? CultureInfo.CurrentCulture); + } + return value.Value.ToString(culture ?? CultureInfo.CurrentCulture); } From 4326250e8e374813d6365c620215d9b6740feb17 Mon Sep 17 00:00:00 2001 From: Benjamin Vertonghen Date: Thu, 30 Jan 2025 13:26:49 +0100 Subject: [PATCH 02/16] Format : Double --- src/Components/Components/src/BindConverter.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Components/Components/src/BindConverter.cs b/src/Components/Components/src/BindConverter.cs index a59d49f4b800..fdbd4c710398 100644 --- a/src/Components/Components/src/BindConverter.cs +++ b/src/Components/Components/src/BindConverter.cs @@ -259,10 +259,15 @@ private static string FormatFloatValueCore(float value, CultureInfo? culture, st /// /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static string? FormatValue(double value, CultureInfo? culture = null) => FormatDoubleValueCore(value, culture); + public static string? FormatValue(double value, CultureInfo? culture = null, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format = null) => FormatDoubleValueCore(value, culture, format); - private static string FormatDoubleValueCore(double value, CultureInfo? culture) + private static string FormatDoubleValueCore(double value, CultureInfo? culture, string? format) { + if (format != null) + { + return value.ToString(format, culture ?? CultureInfo.CurrentCulture); + } + return value.ToString(culture ?? CultureInfo.CurrentCulture); } @@ -275,15 +280,20 @@ private static string FormatDoubleValueCore(double value, CultureInfo? culture) /// /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static string? FormatValue(double? value, CultureInfo? culture = null) => FormatNullableDoubleValueCore(value, culture); + public static string? FormatValue(double? value, CultureInfo? culture = null, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format = null) => FormatNullableDoubleValueCore(value, culture, format); - private static string? FormatNullableDoubleValueCore(double? value, CultureInfo? culture) + private static string? FormatNullableDoubleValueCore(double? value, CultureInfo? culture, string? format) { if (value == null) { return null; } + if (format != null) + { + return value.ToString(format, culture ?? CultureInfo.CurrentCulture); + } + return value.Value.ToString(culture ?? CultureInfo.CurrentCulture); } From 07e51855ad836162156a8cf69e3ea28d1c5347c3 Mon Sep 17 00:00:00 2001 From: Benjamin Vertonghen Date: Thu, 30 Jan 2025 13:27:00 +0100 Subject: [PATCH 03/16] Format : Decimal --- .../Components/src/BindConverter.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Components/Components/src/BindConverter.cs b/src/Components/Components/src/BindConverter.cs index fdbd4c710398..7ef279f6bae0 100644 --- a/src/Components/Components/src/BindConverter.cs +++ b/src/Components/Components/src/BindConverter.cs @@ -306,10 +306,15 @@ private static string FormatDoubleValueCore(double value, CultureInfo? culture, /// /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static string FormatValue(decimal value, CultureInfo? culture = null) => FormatDecimalValueCore(value, culture); + public static string FormatValue(decimal value, CultureInfo? culture = null, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format = null) => FormatDecimalValueCore(value, culture, format); - private static string FormatDecimalValueCore(decimal value, CultureInfo? culture) + private static string FormatDecimalValueCore(decimal value, CultureInfo? culture, string? format) { + if (format != null) + { + return value.ToString(format, culture ?? CultureInfo.CurrentCulture); + } + return value.ToString(culture ?? CultureInfo.CurrentCulture); } @@ -319,18 +324,24 @@ private static string FormatDecimalValueCore(decimal value, CultureInfo? culture /// The value to format. /// /// The to use while formatting. Defaults to . + /// The to use while formatting. /// /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static string? FormatValue(decimal? value, CultureInfo? culture = null) => FormatNullableDecimalValueCore(value, culture); + public static string? FormatValue(decimal? value, CultureInfo? culture = null, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format = null) => FormatNullableDecimalValueCore(value, culture, format); - private static string? FormatNullableDecimalValueCore(decimal? value, CultureInfo? culture) + private static string? FormatNullableDecimalValueCore(decimal? value, CultureInfo? culture, string? format) { if (value == null) { return null; } + if (format != null) + { + return value.ToString(format, culture ?? CultureInfo.CurrentCulture); + } + return value.Value.ToString(culture ?? CultureInfo.CurrentCulture); } From 4b4cf33714e3399aeb7faa3394da87d857b9fa80 Mon Sep 17 00:00:00 2001 From: Benjamin Vertonghen Date: Thu, 30 Jan 2025 13:33:36 +0100 Subject: [PATCH 04/16] Documentation of additional format param. --- src/Components/Components/src/BindConverter.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Components/Components/src/BindConverter.cs b/src/Components/Components/src/BindConverter.cs index 7ef279f6bae0..a8ec10919e3c 100644 --- a/src/Components/Components/src/BindConverter.cs +++ b/src/Components/Components/src/BindConverter.cs @@ -210,6 +210,7 @@ private static string FormatShortValueCore(short value, CultureInfo? culture) /// /// The to use while formatting. Defaults to . /// + /// The format to use. Provided to . /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static string FormatValue(float value, CultureInfo? culture = null, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format = null) => FormatFloatValueCore(value, culture, format); @@ -231,6 +232,7 @@ private static string FormatFloatValueCore(float value, CultureInfo? culture, st /// /// The to use while formatting. Defaults to . /// + /// The format to use. Provided to . /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static string? FormatValue(float? value, CultureInfo? culture = null, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format = null) => FormatNullableFloatValueCore(value, culture, format); @@ -257,6 +259,7 @@ private static string FormatFloatValueCore(float value, CultureInfo? culture, st /// /// The to use while formatting. Defaults to . /// + /// The format to use. Provided to . /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static string? FormatValue(double value, CultureInfo? culture = null, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format = null) => FormatDoubleValueCore(value, culture, format); @@ -278,6 +281,7 @@ private static string FormatDoubleValueCore(double value, CultureInfo? culture, /// /// The to use while formatting. Defaults to . /// + /// The format to use. Provided to . /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static string? FormatValue(double? value, CultureInfo? culture = null, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format = null) => FormatNullableDoubleValueCore(value, culture, format); @@ -304,6 +308,7 @@ private static string FormatDoubleValueCore(double value, CultureInfo? culture, /// /// The to use while formatting. Defaults to . /// + /// The format to use. Provided to . /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static string FormatValue(decimal value, CultureInfo? culture = null, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format = null) => FormatDecimalValueCore(value, culture, format); @@ -326,6 +331,7 @@ private static string FormatDecimalValueCore(decimal value, CultureInfo? culture /// The to use while formatting. Defaults to . /// The to use while formatting. /// + /// The format to use. Provided to . /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static string? FormatValue(decimal? value, CultureInfo? culture = null, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format = null) => FormatNullableDecimalValueCore(value, culture, format); From 835cd1de73731dbd286ad0413685cbc4b988f6fd Mon Sep 17 00:00:00 2001 From: Benjamin Vertonghen Date: Thu, 30 Jan 2025 13:43:38 +0100 Subject: [PATCH 05/16] InputNumber - Add parameter `Format` --- src/Components/Web/src/Forms/InputNumber.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Components/Web/src/Forms/InputNumber.cs b/src/Components/Web/src/Forms/InputNumber.cs index b346b4b39772..3ada22ec19f8 100644 --- a/src/Components/Web/src/Forms/InputNumber.cs +++ b/src/Components/Web/src/Forms/InputNumber.cs @@ -40,6 +40,11 @@ private static string GetStepAttributeValue() /// [Parameter] public string ParsingErrorMessage { get; set; } = "The {0} field must be a number."; + /// + /// Gets or sets the format to be used when displaying a number of types: | | . + /// + [Parameter] public string? Format { get; set; } + /// /// Gets or sets the associated . /// @@ -102,13 +107,13 @@ protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(fa return BindConverter.FormatValue(@short, CultureInfo.InvariantCulture); case float @float: - return BindConverter.FormatValue(@float, CultureInfo.InvariantCulture); + return BindConverter.FormatValue(@float, CultureInfo.InvariantCulture, Format); case double @double: - return BindConverter.FormatValue(@double, CultureInfo.InvariantCulture); + return BindConverter.FormatValue(@double, CultureInfo.InvariantCulture, Format); case decimal @decimal: - return BindConverter.FormatValue(@decimal, CultureInfo.InvariantCulture); + return BindConverter.FormatValue(@decimal, CultureInfo.InvariantCulture, Format); default: throw new InvalidOperationException($"Unsupported type {value.GetType()}"); From c229d0c67dd322b7ec4f072ca7cbc5c55e1ce410 Mon Sep 17 00:00:00 2001 From: Benjamin Vertonghen Date: Thu, 30 Jan 2025 13:46:03 +0100 Subject: [PATCH 06/16] Fix Docs --- src/Components/Components/src/BindConverter.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Components/Components/src/BindConverter.cs b/src/Components/Components/src/BindConverter.cs index a8ec10919e3c..1cc6373cfbff 100644 --- a/src/Components/Components/src/BindConverter.cs +++ b/src/Components/Components/src/BindConverter.cs @@ -329,7 +329,6 @@ private static string FormatDecimalValueCore(decimal value, CultureInfo? culture /// The value to format. /// /// The to use while formatting. Defaults to . - /// The to use while formatting. /// /// The format to use. Provided to . /// The formatted value. From a75422e6500eb22c2894a2900f480a2137a6c530 Mon Sep 17 00:00:00 2001 From: Benjamin Vertonghen Date: Thu, 30 Jan 2025 13:52:35 +0100 Subject: [PATCH 07/16] Fix nullable Value --- src/Components/Components/src/BindConverter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Components/Components/src/BindConverter.cs b/src/Components/Components/src/BindConverter.cs index 1cc6373cfbff..6c453d9cd82d 100644 --- a/src/Components/Components/src/BindConverter.cs +++ b/src/Components/Components/src/BindConverter.cs @@ -246,7 +246,7 @@ private static string FormatFloatValueCore(float value, CultureInfo? culture, st if (format != null) { - return value.ToString(format, culture ?? CultureInfo.CurrentCulture); + return value.Value.ToString(format, culture ?? CultureInfo.CurrentCulture); } return value.Value.ToString(culture ?? CultureInfo.CurrentCulture); @@ -295,7 +295,7 @@ private static string FormatDoubleValueCore(double value, CultureInfo? culture, if (format != null) { - return value.ToString(format, culture ?? CultureInfo.CurrentCulture); + return value.Value.ToString(format, culture ?? CultureInfo.CurrentCulture); } return value.Value.ToString(culture ?? CultureInfo.CurrentCulture); @@ -344,7 +344,7 @@ private static string FormatDecimalValueCore(decimal value, CultureInfo? culture if (format != null) { - return value.ToString(format, culture ?? CultureInfo.CurrentCulture); + return value.Value.ToString(format, culture ?? CultureInfo.CurrentCulture); } return value.Value.ToString(culture ?? CultureInfo.CurrentCulture); From 1cbd5411da7c431b52999473a9f8194d686d1c07 Mon Sep 17 00:00:00 2001 From: Benjamin Vertonghen Date: Thu, 30 Jan 2025 13:53:28 +0100 Subject: [PATCH 08/16] Binders --- src/Components/Components/src/BindConverter.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Components/Components/src/BindConverter.cs b/src/Components/Components/src/BindConverter.cs index 6c453d9cd82d..42a23c1848ba 100644 --- a/src/Components/Components/src/BindConverter.cs +++ b/src/Components/Components/src/BindConverter.cs @@ -1005,7 +1005,9 @@ public static bool TryConvertToNullableFloat(object? obj, CultureInfo? culture, } internal static BindParser ConvertToFloat = ConvertToFloatCore; + internal static BindParserWithFormat ConvertToFloatWithFormat = ConvertToFloatCore; internal static BindParser ConvertToNullableFloat = ConvertToNullableFloatCore; + internal static BindParserWithFormat ConvertToNullableFloatWithFormat = ConvertToNullableFloatCore; private static bool ConvertToFloatCore(object? obj, CultureInfo? culture, out float value) { @@ -1082,7 +1084,9 @@ public static bool TryConvertToNullableDouble(object? obj, CultureInfo? culture, } internal static BindParser ConvertToDoubleDelegate = ConvertToDoubleCore; + internal static BindParserWithFormat ConvertToDoubleWithFormat = ConvertToDoubleCore; internal static BindParser ConvertToNullableDoubleDelegate = ConvertToNullableDoubleCore; + internal static BindParserWithFormat ConvertToNullableDoubleWithFormat = ConvertToNullableDoubleCore; private static bool ConvertToDoubleCore(object? obj, CultureInfo? culture, out double value) { @@ -1159,7 +1163,9 @@ public static bool TryConvertToNullableDecimal(object? obj, CultureInfo? culture } internal static BindParser ConvertToDecimal = ConvertToDecimalCore; + internal static BindParserWithFormat ConvertToDecimalWithFormat = ConvertToDecimalCore; internal static BindParser ConvertToNullableDecimal = ConvertToNullableDecimalCore; + internal static BindParserWithFormat ConvertToNullableDecimalWithFormat = ConvertToNullableDecimalCore; private static bool ConvertToDecimalCore(object? obj, CultureInfo? culture, out decimal value) { From 75bd939ac64c8c2bdbd54cb3ef5dc3ae17d89132 Mon Sep 17 00:00:00 2001 From: Benjamin Vertonghen Date: Thu, 30 Jan 2025 14:11:32 +0100 Subject: [PATCH 09/16] BinderConverter - BindParserWithFormat --- .../Components/src/BindConverter.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/Components/Components/src/BindConverter.cs b/src/Components/Components/src/BindConverter.cs index 42a23c1848ba..66ae5b9c9ea8 100644 --- a/src/Components/Components/src/BindConverter.cs +++ b/src/Components/Components/src/BindConverter.cs @@ -1009,6 +1009,11 @@ public static bool TryConvertToNullableFloat(object? obj, CultureInfo? culture, internal static BindParser ConvertToNullableFloat = ConvertToNullableFloatCore; internal static BindParserWithFormat ConvertToNullableFloatWithFormat = ConvertToNullableFloatCore; + private static bool ConvertToFloatCore(object? obj, CultureInfo? culture, out float value) + { + return ConvertToFloatCore(obj, culture, format: null, out value); + } + private static bool ConvertToFloatCore(object? obj, CultureInfo? culture, out float value) { var text = (string?)obj; @@ -1035,6 +1040,11 @@ private static bool ConvertToFloatCore(object? obj, CultureInfo? culture, out fl } private static bool ConvertToNullableFloatCore(object? obj, CultureInfo? culture, out float? value) + { + return ConvertToNullableFloatCore(obj, culture, format: null, out value); + } + + private static bool ConvertToNullableFloatCore(object? obj, CultureInfo? culture, string? format, out float? value) { var text = (string?)obj; if (string.IsNullOrEmpty(text)) @@ -1089,6 +1099,11 @@ public static bool TryConvertToNullableDouble(object? obj, CultureInfo? culture, internal static BindParserWithFormat ConvertToNullableDoubleWithFormat = ConvertToNullableDoubleCore; private static bool ConvertToDoubleCore(object? obj, CultureInfo? culture, out double value) + { + return ConvertToDoubleCore(obj, culture, format: null, out value); + } + + private static bool ConvertToDoubleCore(object? obj, CultureInfo? culture, string? format, out double value) { var text = (string?)obj; if (string.IsNullOrEmpty(text)) @@ -1114,6 +1129,11 @@ private static bool ConvertToDoubleCore(object? obj, CultureInfo? culture, out d } private static bool ConvertToNullableDoubleCore(object? obj, CultureInfo? culture, out double? value) + { + return ConvertToNullableDoubleCore(obj, culture, format: null, out value); + } + + private static bool ConvertToNullableDoubleCore(object? obj, CultureInfo? culture, string? format, out double? value) { var text = (string?)obj; if (string.IsNullOrEmpty(text)) @@ -1168,6 +1188,11 @@ public static bool TryConvertToNullableDecimal(object? obj, CultureInfo? culture internal static BindParserWithFormat ConvertToNullableDecimalWithFormat = ConvertToNullableDecimalCore; private static bool ConvertToDecimalCore(object? obj, CultureInfo? culture, out decimal value) + { + return ConvertToDecimalCore(obj, culture, format: null, out value); + } + + private static bool ConvertToDecimalCore(object? obj, CultureInfo? culture, string? format, out decimal value) { var text = (string?)obj; if (string.IsNullOrEmpty(text)) @@ -1187,6 +1212,11 @@ private static bool ConvertToDecimalCore(object? obj, CultureInfo? culture, out } private static bool ConvertToNullableDecimalCore(object? obj, CultureInfo? culture, out decimal? value) + { + return ConvertToNullableDecimalCore(obj, culture, format: null, out value); + } + + private static bool ConvertToNullableDecimalCore(object? obj, CultureInfo? culture, string? format, out decimal? value) { var text = (string?)obj; if (string.IsNullOrEmpty(text)) From 2b98c9f27f505ba747e251cf02527ad593b25b36 Mon Sep 17 00:00:00 2001 From: Benjamin Vertonghen Date: Thu, 30 Jan 2025 14:35:47 +0100 Subject: [PATCH 10/16] Complete Bind-Converter --- .../Components/src/BindConverter.cs | 114 +++++++++++++++++- 1 file changed, 112 insertions(+), 2 deletions(-) diff --git a/src/Components/Components/src/BindConverter.cs b/src/Components/Components/src/BindConverter.cs index 66ae5b9c9ea8..65148b0921c0 100644 --- a/src/Components/Components/src/BindConverter.cs +++ b/src/Components/Components/src/BindConverter.cs @@ -24,7 +24,6 @@ public static class BindConverter private static readonly object BoxedFalse = false; private delegate object? BindFormatter(T value, CultureInfo? culture); - internal delegate bool BindParser(object? obj, CultureInfo? culture, [MaybeNullWhen(false)] out T value); internal delegate bool BindParserWithFormat(object? obj, CultureInfo? culture, string? format, [MaybeNullWhen(false)] out T value); @@ -203,6 +202,17 @@ private static string FormatShortValueCore(short value, CultureInfo? culture) return value.Value.ToString(culture ?? CultureInfo.CurrentCulture); } + /// + /// Formats the provided for inclusion in an attribute. + /// + /// The value to format. + /// + /// The to use while formatting. Defaults to . + /// + /// The formatted value. + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static string FormatValue(float value, CultureInfo? culture = null) => FormatFloatValueCore(value, culture, format: null); + /// /// Formats the provided for inclusion in an attribute. /// @@ -225,6 +235,22 @@ private static string FormatFloatValueCore(float value, CultureInfo? culture, st return value.ToString(culture ?? CultureInfo.CurrentCulture); } + private static string FormatFloatValueCore(float value, CultureInfo? culture) + { + return value.ToString(culture ?? CultureInfo.CurrentCulture); + } + + /// + /// Formats the provided for inclusion in an attribute. + /// + /// The value to format. + /// + /// The to use while formatting. Defaults to . + /// + /// The formatted value. + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static string? FormatValue(float? value, CultureInfo? culture = null) => FormatNullableFloatValueCore(value, culture, format: null); + /// /// Formats the provided for inclusion in an attribute. /// @@ -252,6 +278,27 @@ private static string FormatFloatValueCore(float value, CultureInfo? culture, st return value.Value.ToString(culture ?? CultureInfo.CurrentCulture); } + private static string? FormatNullableFloatValueCore(float? value, CultureInfo? culture) + { + if (value == null) + { + return null; + } + + return value.Value.ToString(culture ?? CultureInfo.CurrentCulture); + } + + /// + /// Formats the provided for inclusion in an attribute. + /// + /// The value to format. + /// + /// The to use while formatting. Defaults to . + /// + /// The formatted value. + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static string? FormatValue(double value, CultureInfo? culture = null) => FormatDoubleValueCore(value, culture, format: null); + /// /// Formats the provided for inclusion in an attribute. /// @@ -274,6 +321,22 @@ private static string FormatDoubleValueCore(double value, CultureInfo? culture, return value.ToString(culture ?? CultureInfo.CurrentCulture); } + private static string FormatDoubleValueCore(double value, CultureInfo? culture) + { + return value.ToString(culture ?? CultureInfo.CurrentCulture); + } + + /// + /// Formats the provided for inclusion in an attribute. + /// + /// The value to format. + /// + /// The to use while formatting. Defaults to . + /// + /// The formatted value. + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static string? FormatValue(double? value, CultureInfo? culture = null) => FormatNullableDoubleValueCore(value, culture, format: null); + /// /// Formats the provided for inclusion in an attribute. /// @@ -286,6 +349,16 @@ private static string FormatDoubleValueCore(double value, CultureInfo? culture, [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static string? FormatValue(double? value, CultureInfo? culture = null, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format = null) => FormatNullableDoubleValueCore(value, culture, format); + private static string? FormatNullableDoubleValueCore(double? value, CultureInfo? culture) + { + if (value == null) + { + return null; + } + + return value.Value.ToString(culture ?? CultureInfo.CurrentCulture); + } + private static string? FormatNullableDoubleValueCore(double? value, CultureInfo? culture, string? format) { if (value == null) @@ -301,6 +374,17 @@ private static string FormatDoubleValueCore(double value, CultureInfo? culture, return value.Value.ToString(culture ?? CultureInfo.CurrentCulture); } + /// + /// Formats the provided for inclusion in an attribute. + /// + /// The value to format. + /// + /// The to use while formatting. Defaults to . + /// + /// The formatted value. + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static string FormatValue(decimal value, CultureInfo? culture = null) => FormatDecimalValueCore(value, culture, format: null); + /// /// Formats the provided for inclusion in an attribute. /// @@ -313,6 +397,11 @@ private static string FormatDoubleValueCore(double value, CultureInfo? culture, [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static string FormatValue(decimal value, CultureInfo? culture = null, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format = null) => FormatDecimalValueCore(value, culture, format); + private static string FormatDecimalValueCore(decimal value, CultureInfo? culture) + { + return value.ToString(culture ?? CultureInfo.CurrentCulture); + } + private static string FormatDecimalValueCore(decimal value, CultureInfo? culture, string? format) { if (format != null) @@ -323,6 +412,17 @@ private static string FormatDecimalValueCore(decimal value, CultureInfo? culture return value.ToString(culture ?? CultureInfo.CurrentCulture); } + /// + /// Formats the provided as a . + /// + /// The value to format. + /// + /// The to use while formatting. Defaults to . + /// + /// The formatted value. + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static string? FormatValue(decimal? value, CultureInfo? culture = null) => FormatNullableDecimalValueCore(value, culture, format: null); + /// /// Formats the provided as a . /// @@ -335,6 +435,16 @@ private static string FormatDecimalValueCore(decimal value, CultureInfo? culture [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static string? FormatValue(decimal? value, CultureInfo? culture = null, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format = null) => FormatNullableDecimalValueCore(value, culture, format); + private static string? FormatNullableDecimalValueCore(decimal? value, CultureInfo? culture) + { + if (value == null) + { + return null; + } + + return value.Value.ToString(culture ?? CultureInfo.CurrentCulture); + } + private static string? FormatNullableDecimalValueCore(decimal? value, CultureInfo? culture, string? format) { if (value == null) @@ -1014,7 +1124,7 @@ private static bool ConvertToFloatCore(object? obj, CultureInfo? culture, out fl return ConvertToFloatCore(obj, culture, format: null, out value); } - private static bool ConvertToFloatCore(object? obj, CultureInfo? culture, out float value) + private static bool ConvertToFloatCore(object? obj, CultureInfo? culture, string? format, out float value) { var text = (string?)obj; if (string.IsNullOrEmpty(text)) From a0c2130f177c85ffe3ad804659503c92a787698b Mon Sep 17 00:00:00 2001 From: Benjamin Vertonghen Date: Thu, 30 Jan 2025 15:39:02 +0100 Subject: [PATCH 11/16] Realign format --- src/Components/Components/src/BindConverter.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Components/Components/src/BindConverter.cs b/src/Components/Components/src/BindConverter.cs index 65148b0921c0..c769b1c1737d 100644 --- a/src/Components/Components/src/BindConverter.cs +++ b/src/Components/Components/src/BindConverter.cs @@ -223,7 +223,7 @@ private static string FormatShortValueCore(short value, CultureInfo? culture) /// The format to use. Provided to . /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static string FormatValue(float value, CultureInfo? culture = null, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format = null) => FormatFloatValueCore(value, culture, format); + public static string FormatValue(float value, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string format, CultureInfo? culture = null) => FormatFloatValueCore(value, culture, format); private static string FormatFloatValueCore(float value, CultureInfo? culture, string? format) { @@ -261,7 +261,7 @@ private static string FormatFloatValueCore(float value, CultureInfo? culture) /// The format to use. Provided to . /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static string? FormatValue(float? value, CultureInfo? culture = null, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format = null) => FormatNullableFloatValueCore(value, culture, format); + public static string? FormatValue(float? value, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string format, CultureInfo? culture = null) => FormatNullableFloatValueCore(value, culture, format); private static string? FormatNullableFloatValueCore(float? value, CultureInfo? culture, string? format) { @@ -309,7 +309,7 @@ private static string FormatFloatValueCore(float value, CultureInfo? culture) /// The format to use. Provided to . /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static string? FormatValue(double value, CultureInfo? culture = null, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format = null) => FormatDoubleValueCore(value, culture, format); + public static string? FormatValue(double value, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string format, CultureInfo? culture = null) => FormatDoubleValueCore(value, culture, format); private static string FormatDoubleValueCore(double value, CultureInfo? culture, string? format) { @@ -347,7 +347,7 @@ private static string FormatDoubleValueCore(double value, CultureInfo? culture) /// The format to use. Provided to . /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static string? FormatValue(double? value, CultureInfo? culture = null, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format = null) => FormatNullableDoubleValueCore(value, culture, format); + public static string? FormatValue(double? value, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string format, CultureInfo? culture = null) => FormatNullableDoubleValueCore(value, culture, format); private static string? FormatNullableDoubleValueCore(double? value, CultureInfo? culture) { @@ -395,7 +395,7 @@ private static string FormatDoubleValueCore(double value, CultureInfo? culture) /// The format to use. Provided to . /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static string FormatValue(decimal value, CultureInfo? culture = null, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format = null) => FormatDecimalValueCore(value, culture, format); + public static string FormatValue(decimal value, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string format, CultureInfo? culture = null) => FormatDecimalValueCore(value, culture, format); private static string FormatDecimalValueCore(decimal value, CultureInfo? culture) { @@ -433,7 +433,7 @@ private static string FormatDecimalValueCore(decimal value, CultureInfo? culture /// The format to use. Provided to . /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static string? FormatValue(decimal? value, CultureInfo? culture = null, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format = null) => FormatNullableDecimalValueCore(value, culture, format); + public static string? FormatValue(decimal? value, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string format, CultureInfo? culture = null) => FormatNullableDecimalValueCore(value, culture, format); private static string? FormatNullableDecimalValueCore(decimal? value, CultureInfo? culture) { From 7b7657b69bb9ce4932ebd96714bffdf01153d528 Mon Sep 17 00:00:00 2001 From: Benjamin Vertonghen Date: Thu, 30 Jan 2025 16:15:18 +0100 Subject: [PATCH 12/16] Add API Docs --- src/Components/Components/src/PublicAPI.Unshipped.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/Components/Components/src/PublicAPI.Unshipped.txt index 7dc5c58110bf..bb9ce130ce89 100644 --- a/src/Components/Components/src/PublicAPI.Unshipped.txt +++ b/src/Components/Components/src/PublicAPI.Unshipped.txt @@ -1 +1,7 @@ #nullable enable +static Microsoft.AspNetCore.Components.BindConverter.FormatValue(decimal value, string! format, System.Globalization.CultureInfo? culture = null) -> string! +static Microsoft.AspNetCore.Components.BindConverter.FormatValue(decimal? value, string! format, System.Globalization.CultureInfo? culture = null) -> string? +static Microsoft.AspNetCore.Components.BindConverter.FormatValue(double value, string! format, System.Globalization.CultureInfo? culture = null) -> string? +static Microsoft.AspNetCore.Components.BindConverter.FormatValue(double? value, string! format, System.Globalization.CultureInfo? culture = null) -> string? +static Microsoft.AspNetCore.Components.BindConverter.FormatValue(float value, string! format, System.Globalization.CultureInfo? culture = null) -> string! +static Microsoft.AspNetCore.Components.BindConverter.FormatValue(float? value, string! format, System.Globalization.CultureInfo? culture = null) -> string? From 86bccd7fb7e2fa8f7222a677c39e634318647757 Mon Sep 17 00:00:00 2001 From: Benjamin Vertonghen Date: Thu, 30 Jan 2025 16:15:25 +0100 Subject: [PATCH 13/16] Add BindConverter Tests --- .../Components/test/BindConverterTest.cs | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/Components/Components/test/BindConverterTest.cs b/src/Components/Components/test/BindConverterTest.cs index b18b71dc5684..ed10a4040278 100644 --- a/src/Components/Components/test/BindConverterTest.cs +++ b/src/Components/Components/test/BindConverterTest.cs @@ -369,6 +369,75 @@ public void TryConvertTo_NullableGuid__Invalid() Assert.Null(actual); } + [Theory] + [InlineData(1.1, "0.0#", "1.1")] // Single decimal place with optional second + [InlineData(1500, "0.00", "1500.00")] // Force two decimal places + [InlineData(1500, "0.##", "1500")] // Remove unnecessary decimals + [InlineData(0, "0.00", "0.00")] // Zero with fixed decimals + [InlineData(0, "0.##", "0")] // Zero with optional decimals + [InlineData(-1.1, "0.0#", "-1.1")] // Negative number with one decimal place + [InlineData(-1500, "0.00", "-1500.00")] // Negative number with two fixed decimals + [InlineData(1.999, "0.0", "2.0")] // Rounding up + [InlineData(1.111, "0.0", "1.1")] // Rounding down + [InlineData(1234567.89, "N2", "1,234,567.89")] // Large number with thousands separator + [InlineData(1234567.89, "#,##0.00", "1,234,567.89")] // Explicit thousands separator format + [InlineData(0.1234, "0.00%", "12.34%")] // Percentage formatting + [InlineData(0.12, "00.00", "00.12")] // Fixed zero's with fixed decimals + [InlineData(1234567.89, "0.00", "1234567.89")] // Fixed two decimals + public void FormatValue_Double_Format(double value, string format, string expected) + { + // Act + var actual = BindConverter.FormatValue(value, format, CultureInfo.InvariantCulture); + + // Assert + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(1.1, "0.0#", "1.1")] // Single decimal place with optional second + [InlineData(1500, "0.00", "1500.00")] // Force two decimal places + [InlineData(1500, "0.##", "1500")] // Remove unnecessary decimals + [InlineData(0, "0.00", "0.00")] // Zero with fixed decimals + [InlineData(0, "0.##", "0")] // Zero with optional decimals + [InlineData(-1.1, "0.0#", "-1.1")] // Negative number with one decimal place + [InlineData(-1500, "0.00", "-1500.00")] // Negative number with two fixed decimals + [InlineData(1.999, "0.0", "2.0")] // Rounding up + [InlineData(1.111, "0.0", "1.1")] // Rounding down + [InlineData(1234567.89, "N2", "1,234,567.89")] // Large number with thousands separator + [InlineData(1234567.89, "#,##0.00", "1,234,567.89")] // Explicit thousands separator format + [InlineData(0.1234, "0.00%", "12.34%")] // Percentage formatting + [InlineData(0.12, "00.00", "00.12")] // Fixed zero's with fixed decimals + public void FormatValue_Decimal_Format(decimal value, string format, string expected) + { + // Act + var actual = BindConverter.FormatValue(value, format, CultureInfo.InvariantCulture); + + // Assert + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(1.1, "0.0#", "1.1")] // Single decimal place with optional second + [InlineData(1500, "0.00", "1500.00")] // Force two decimal places + [InlineData(1500, "0.##", "1500")] // Remove unnecessary decimals + [InlineData(0, "0.00", "0.00")] // Zero with fixed decimals + [InlineData(0, "0.##", "0")] // Zero with optional decimals + [InlineData(-1.1, "0.0#", "-1.1")] // Negative number with one decimal place + [InlineData(-1500, "0.00", "-1500.00")] // Negative number with two fixed decimals + [InlineData(1.999, "0.0", "2.0")] // Rounding up + [InlineData(1.111, "0.0", "1.1")] // Rounding down + [InlineData(1234567.89, "N2", "1,234,567.88")] // Large number with thousands separator + [InlineData(0.1234, "0.00%", "12.34%")] // Percentage formatting + [InlineData(0.12, "00.00", "00.12")] // Fixed zero's with fixed decimals + public void FormatValue_Float_Format(float value, string format, string expected) + { + // Act + var actual = BindConverter.FormatValue(value, format, CultureInfo.InvariantCulture); + + // Assert + Assert.Equal(expected, actual); + } + private enum SomeLetters { A, From a3200ce1ec6bfa47733b95f79acd76d423e91aaf Mon Sep 17 00:00:00 2001 From: Benjamin Vertonghen Date: Thu, 30 Jan 2025 16:24:58 +0100 Subject: [PATCH 14/16] Nullable format --- src/Components/Components/src/BindConverter.cs | 14 +++++++------- .../Components/src/PublicAPI.Unshipped.txt | 13 +++++++------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/Components/Components/src/BindConverter.cs b/src/Components/Components/src/BindConverter.cs index c769b1c1737d..05aa9185d467 100644 --- a/src/Components/Components/src/BindConverter.cs +++ b/src/Components/Components/src/BindConverter.cs @@ -223,7 +223,7 @@ private static string FormatShortValueCore(short value, CultureInfo? culture) /// The format to use. Provided to . /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static string FormatValue(float value, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string format, CultureInfo? culture = null) => FormatFloatValueCore(value, culture, format); + public static string FormatValue(float value, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, CultureInfo? culture = null) => FormatFloatValueCore(value, culture, format); private static string FormatFloatValueCore(float value, CultureInfo? culture, string? format) { @@ -261,7 +261,7 @@ private static string FormatFloatValueCore(float value, CultureInfo? culture) /// The format to use. Provided to . /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static string? FormatValue(float? value, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string format, CultureInfo? culture = null) => FormatNullableFloatValueCore(value, culture, format); + public static string? FormatValue(float? value, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, CultureInfo? culture = null) => FormatNullableFloatValueCore(value, culture, format); private static string? FormatNullableFloatValueCore(float? value, CultureInfo? culture, string? format) { @@ -309,7 +309,7 @@ private static string FormatFloatValueCore(float value, CultureInfo? culture) /// The format to use. Provided to . /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static string? FormatValue(double value, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string format, CultureInfo? culture = null) => FormatDoubleValueCore(value, culture, format); + public static string? FormatValue(double value, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, CultureInfo? culture = null) => FormatDoubleValueCore(value, culture, format); private static string FormatDoubleValueCore(double value, CultureInfo? culture, string? format) { @@ -347,7 +347,7 @@ private static string FormatDoubleValueCore(double value, CultureInfo? culture) /// The format to use. Provided to . /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static string? FormatValue(double? value, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string format, CultureInfo? culture = null) => FormatNullableDoubleValueCore(value, culture, format); + public static string? FormatValue(double? value, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, CultureInfo? culture = null) => FormatNullableDoubleValueCore(value, culture, format); private static string? FormatNullableDoubleValueCore(double? value, CultureInfo? culture) { @@ -395,7 +395,7 @@ private static string FormatDoubleValueCore(double value, CultureInfo? culture) /// The format to use. Provided to . /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static string FormatValue(decimal value, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string format, CultureInfo? culture = null) => FormatDecimalValueCore(value, culture, format); + public static string FormatValue(decimal value, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, CultureInfo? culture = null) => FormatDecimalValueCore(value, culture, format); private static string FormatDecimalValueCore(decimal value, CultureInfo? culture) { @@ -433,7 +433,7 @@ private static string FormatDecimalValueCore(decimal value, CultureInfo? culture /// The format to use. Provided to . /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static string? FormatValue(decimal? value, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string format, CultureInfo? culture = null) => FormatNullableDecimalValueCore(value, culture, format); + public static string? FormatValue(decimal? value, [StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, CultureInfo? culture = null) => FormatNullableDecimalValueCore(value, culture, format); private static string? FormatNullableDecimalValueCore(decimal? value, CultureInfo? culture) { @@ -481,7 +481,7 @@ private static string FormatDecimalValueCore(decimal value, CultureInfo? culture /// /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static string FormatValue(DateTime value, [StringSyntax(StringSyntaxAttribute.DateTimeFormat)] string format, CultureInfo? culture = null) => FormatDateTimeValueCore(value, format, culture); + public static string FormatValue(DateTime value, [StringSyntax(StringSyntaxAttribute.DateTimeFormat)] string? format, CultureInfo? culture = null) => FormatDateTimeValueCore(value, format, culture); private static string FormatDateTimeValueCore(DateTime value, string? format, CultureInfo? culture) { diff --git a/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/Components/Components/src/PublicAPI.Unshipped.txt index bb9ce130ce89..6ed828707a54 100644 --- a/src/Components/Components/src/PublicAPI.Unshipped.txt +++ b/src/Components/Components/src/PublicAPI.Unshipped.txt @@ -1,7 +1,8 @@ #nullable enable -static Microsoft.AspNetCore.Components.BindConverter.FormatValue(decimal value, string! format, System.Globalization.CultureInfo? culture = null) -> string! -static Microsoft.AspNetCore.Components.BindConverter.FormatValue(decimal? value, string! format, System.Globalization.CultureInfo? culture = null) -> string? -static Microsoft.AspNetCore.Components.BindConverter.FormatValue(double value, string! format, System.Globalization.CultureInfo? culture = null) -> string? -static Microsoft.AspNetCore.Components.BindConverter.FormatValue(double? value, string! format, System.Globalization.CultureInfo? culture = null) -> string? -static Microsoft.AspNetCore.Components.BindConverter.FormatValue(float value, string! format, System.Globalization.CultureInfo? culture = null) -> string! -static Microsoft.AspNetCore.Components.BindConverter.FormatValue(float? value, string! format, System.Globalization.CultureInfo? culture = null) -> string? +static Microsoft.AspNetCore.Components.BindConverter.FormatValue(decimal value, string? format, System.Globalization.CultureInfo? culture = null) -> string! +static Microsoft.AspNetCore.Components.BindConverter.FormatValue(decimal? value, string? format, System.Globalization.CultureInfo? culture = null) -> string? +static Microsoft.AspNetCore.Components.BindConverter.FormatValue(double value, string? format, System.Globalization.CultureInfo? culture = null) -> string? +static Microsoft.AspNetCore.Components.BindConverter.FormatValue(double? value, string? format, System.Globalization.CultureInfo? culture = null) -> string? +static Microsoft.AspNetCore.Components.BindConverter.FormatValue(float value, string? format, System.Globalization.CultureInfo? culture = null) -> string! +static Microsoft.AspNetCore.Components.BindConverter.FormatValue(float? value, string? format, System.Globalization.CultureInfo? culture = null) -> string? +static Microsoft.AspNetCore.Components.BindConverter.FormatValue(System.DateTime value, string? format, System.Globalization.CultureInfo? culture = null) -> string! From 26b513dfb6adbc52d640f68e3399d3d7e6de05fb Mon Sep 17 00:00:00 2001 From: Benjamin Vertonghen Date: Thu, 30 Jan 2025 16:25:09 +0100 Subject: [PATCH 15/16] InputNumber --- src/Components/Web/src/Forms/InputNumber.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Components/Web/src/Forms/InputNumber.cs b/src/Components/Web/src/Forms/InputNumber.cs index 3ada22ec19f8..9f894a45dc3d 100644 --- a/src/Components/Web/src/Forms/InputNumber.cs +++ b/src/Components/Web/src/Forms/InputNumber.cs @@ -107,13 +107,13 @@ protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(fa return BindConverter.FormatValue(@short, CultureInfo.InvariantCulture); case float @float: - return BindConverter.FormatValue(@float, CultureInfo.InvariantCulture, Format); + return BindConverter.FormatValue(@float, Format, CultureInfo.InvariantCulture); case double @double: - return BindConverter.FormatValue(@double, CultureInfo.InvariantCulture, Format); + return BindConverter.FormatValue(@double, Format, CultureInfo.InvariantCulture); case decimal @decimal: - return BindConverter.FormatValue(@decimal, CultureInfo.InvariantCulture, Format); + return BindConverter.FormatValue(@decimal, Format, CultureInfo.InvariantCulture); default: throw new InvalidOperationException($"Unsupported type {value.GetType()}"); From bcc65fdb35bfbf4599fe2a5fc9e5a7b9c8b4e19e Mon Sep 17 00:00:00 2001 From: Benjamin Vertonghen Date: Thu, 30 Jan 2025 17:20:51 +0100 Subject: [PATCH 16/16] Tests for input --- .../Components/src/BindConverter.cs | 2 +- .../Components/src/PublicAPI.Unshipped.txt | 3 +- src/Components/Web/src/Forms/InputNumber.cs | 2 +- .../Web/src/PublicAPI.Unshipped.txt | 2 + .../Web/test/Forms/InputNumberTest.cs | 76 +++++++++++++++---- 5 files changed, 67 insertions(+), 18 deletions(-) diff --git a/src/Components/Components/src/BindConverter.cs b/src/Components/Components/src/BindConverter.cs index 05aa9185d467..5b83b2cb1017 100644 --- a/src/Components/Components/src/BindConverter.cs +++ b/src/Components/Components/src/BindConverter.cs @@ -481,7 +481,7 @@ private static string FormatDecimalValueCore(decimal value, CultureInfo? culture /// /// The formatted value. [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static string FormatValue(DateTime value, [StringSyntax(StringSyntaxAttribute.DateTimeFormat)] string? format, CultureInfo? culture = null) => FormatDateTimeValueCore(value, format, culture); + public static string FormatValue(DateTime value, [StringSyntax(StringSyntaxAttribute.DateTimeFormat)] string format, CultureInfo? culture = null) => FormatDateTimeValueCore(value, format, culture); private static string FormatDateTimeValueCore(DateTime value, string? format, CultureInfo? culture) { diff --git a/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/Components/Components/src/PublicAPI.Unshipped.txt index 6ed828707a54..b0f8a9e2c389 100644 --- a/src/Components/Components/src/PublicAPI.Unshipped.txt +++ b/src/Components/Components/src/PublicAPI.Unshipped.txt @@ -4,5 +4,4 @@ static Microsoft.AspNetCore.Components.BindConverter.FormatValue(decimal? value, static Microsoft.AspNetCore.Components.BindConverter.FormatValue(double value, string? format, System.Globalization.CultureInfo? culture = null) -> string? static Microsoft.AspNetCore.Components.BindConverter.FormatValue(double? value, string? format, System.Globalization.CultureInfo? culture = null) -> string? static Microsoft.AspNetCore.Components.BindConverter.FormatValue(float value, string? format, System.Globalization.CultureInfo? culture = null) -> string! -static Microsoft.AspNetCore.Components.BindConverter.FormatValue(float? value, string? format, System.Globalization.CultureInfo? culture = null) -> string? -static Microsoft.AspNetCore.Components.BindConverter.FormatValue(System.DateTime value, string? format, System.Globalization.CultureInfo? culture = null) -> string! +static Microsoft.AspNetCore.Components.BindConverter.FormatValue(float? value, string? format, System.Globalization.CultureInfo? culture = null) -> string? \ No newline at end of file diff --git a/src/Components/Web/src/Forms/InputNumber.cs b/src/Components/Web/src/Forms/InputNumber.cs index 9f894a45dc3d..194b7fdc078e 100644 --- a/src/Components/Web/src/Forms/InputNumber.cs +++ b/src/Components/Web/src/Forms/InputNumber.cs @@ -41,7 +41,7 @@ private static string GetStepAttributeValue() [Parameter] public string ParsingErrorMessage { get; set; } = "The {0} field must be a number."; /// - /// Gets or sets the format to be used when displaying a number of types: | | . + /// Gets or sets the format to be used when displaying a number of types: , , . /// [Parameter] public string? Format { get; set; } diff --git a/src/Components/Web/src/PublicAPI.Unshipped.txt b/src/Components/Web/src/PublicAPI.Unshipped.txt index 7dc5c58110bf..b77f05245715 100644 --- a/src/Components/Web/src/PublicAPI.Unshipped.txt +++ b/src/Components/Web/src/PublicAPI.Unshipped.txt @@ -1 +1,3 @@ #nullable enable +Microsoft.AspNetCore.Components.Forms.InputNumber.Format.get -> string? +Microsoft.AspNetCore.Components.Forms.InputNumber.Format.set -> void diff --git a/src/Components/Web/test/Forms/InputNumberTest.cs b/src/Components/Web/test/Forms/InputNumberTest.cs index 12a7891b4bb4..ebdf9509482a 100644 --- a/src/Components/Web/test/Forms/InputNumberTest.cs +++ b/src/Components/Web/test/Forms/InputNumberTest.cs @@ -20,21 +20,59 @@ public InputNumberTest() _testRenderer = new TestRenderer(services.BuildServiceProvider()); } + [Theory] + [InlineData("1.1", "0.0#", "1.1")] // Single decimal place with optional second + [InlineData("1500", "0.00", "1500.00")] // Force two decimal places + [InlineData("1500", "0.0000", "1500.0000")] // Force four decimal places + [InlineData("1500", "0.##", "1500")] // Remove unnecessary decimals + [InlineData("0", "0.00", "0.00")] // Zero with fixed decimals + [InlineData("0", "0.##", "0")] // Zero with optional decimals + [InlineData("-1.1", "0.0#", "-1.1")] // Negative number with one decimal place + [InlineData("-1500", "0.00", "-1500.00")] // Negative number with two fixed decimals + [InlineData("1.999", "0.0", "2.0")] // Rounding up + [InlineData("1.111", "0.0", "1.1")] // Rounding down + [InlineData("1234567.89", "N2", "1,234,567.89")] // Large number with thousands separator + [InlineData("1234567.89", "#,##0.00", "1,234,567.89")] // Explicit thousands separator format + [InlineData("0.1234", "0.00%", "12.34%")] // Percentage formatting + [InlineData("0.12", "00.00", "00.12")] // Fixed zero's with fixed decimals + [InlineData("1234567.89", "0.00", "1234567.89")] // Fixed two decimals + public async Task FormatDoubles(string value, string format, string expected) + { + // Arrange + var model = new TestModel(); + var rootComponent = new TestInputHostComponent> + { + EditContext = new EditContext(model), + ValueExpression = () => model.Double, + AdditionalAttributes = new Dictionary + { + { "Format", format } + } + }; + var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent); + + // Act + inputComponent.CurrentValueAsString = value; + + // Assert + Assert.Equal(expected, inputComponent.CurrentValueAsString); + } + [Fact] public async Task ValidationErrorUsesDisplayAttributeName() { // Arrange var model = new TestModel(); - var rootComponent = new TestInputHostComponent + var rootComponent = new TestInputHostComponent> { EditContext = new EditContext(model), - ValueExpression = () => model.SomeNumber, + ValueExpression = () => model.Int, AdditionalAttributes = new Dictionary - { - { "DisplayName", "Some number" } - } + { + { "DisplayName", "Some number" } + } }; - var fieldIdentifier = FieldIdentifier.Create(() => model.SomeNumber); + var fieldIdentifier = FieldIdentifier.Create(() => model.Int); var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent); // Act @@ -51,10 +89,10 @@ public async Task InputElementIsAssignedSuccessfully() { // Arrange var model = new TestModel(); - var rootComponent = new TestInputHostComponent + var rootComponent = new TestInputHostComponent> { EditContext = new EditContext(model), - ValueExpression = () => model.SomeNumber, + ValueExpression = () => model.Int, }; // Act @@ -69,10 +107,10 @@ public async Task UserDefinedTypeAttributeOverridesDefault() { // Arrange var model = new TestModel(); - var hostComponent = new TestInputHostComponent + var hostComponent = new TestInputHostComponent> { EditContext = new EditContext(model), - ValueExpression = () => model.SomeNumber, + ValueExpression = () => model.Int, AdditionalAttributes = new Dictionary { { "type", "range" } // User-defined 'type' attribute to override default @@ -93,21 +131,31 @@ public async Task UserDefinedTypeAttributeOverridesDefault() Assert.Equal("range", typeAttributeFrame.AttributeValue); } - private async Task RenderAndGetTestInputNumberComponentIdAsync(TestInputHostComponent hostComponent) + private async Task RenderAndGetTestInputNumberComponentIdAsync(TestInputHostComponent> hostComponent) { var hostComponentId = _testRenderer.AssignRootComponentId(hostComponent); await _testRenderer.RenderRootComponentAsync(hostComponentId); var batch = _testRenderer.Batches.Single(); - return batch.GetComponentFrames().Single().ComponentId; + return batch.GetComponentFrames>().Single().ComponentId; } private class TestModel { - public int SomeNumber { get; set; } + public int Int { get; set; } + public double Double { get; set; } + public float Float { get; set; } + public decimal Decimal { get; set; } } - private class TestInputNumberComponent : InputNumber + class TestInputNumberComponent : InputNumber { + public new TValue CurrentValue => base.CurrentValue; + + public new string CurrentValueAsString + { + get => base.CurrentValueAsString; + set => base.CurrentValueAsString = value; + } public async Task SetCurrentValueAsStringAsync(string value) { // This is equivalent to the subclass writing to CurrentValueAsString