From ba708e95367c5cb5d7d4c3885ed27d152a1225b4 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 3 Dec 2025 09:46:00 +0000
Subject: [PATCH 1/4] Initial plan
From 4de00ab2fb2739d96f84651e7a90a9979f5ed5c5 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 3 Dec 2025 09:56:27 +0000
Subject: [PATCH 2/4] Implement auto-select input type in InputDate based on
TValue
Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com>
---
src/Components/Web/src/Forms/InputDate.cs | 26 ++-
.../Web/src/PublicAPI.Unshipped.txt | 2 +
.../Web/test/Forms/InputDateTest.cs | 184 +++++++++++++++++-
3 files changed, 208 insertions(+), 4 deletions(-)
diff --git a/src/Components/Web/src/Forms/InputDate.cs b/src/Components/Web/src/Forms/InputDate.cs
index 4498dd539b48..131aebb06b8d 100644
--- a/src/Components/Web/src/Forms/InputDate.cs
+++ b/src/Components/Web/src/Forms/InputDate.cs
@@ -31,8 +31,14 @@ public class InputDate<[DynamicallyAccessedMembers(DynamicallyAccessedMemberType
///
/// Gets or sets the type of HTML input to be rendered.
+ /// If not specified, the type is automatically inferred based on :
+ ///
+ /// - and default to
+ /// - defaults to
+ /// - defaults to
+ ///
///
- [Parameter] public InputDateType Type { get; set; } = InputDateType.Date;
+ [Parameter] public InputDateType? Type { get; set; }
///
/// Gets or sets the error message used when displaying an a parsing error.
@@ -66,13 +72,15 @@ public InputDate()
///
protected override void OnParametersSet()
{
- (_typeAttributeValue, _format, var formatDescription) = Type switch
+ var effectiveType = Type ?? GetDefaultInputDateType();
+
+ (_typeAttributeValue, _format, var formatDescription) = effectiveType switch
{
InputDateType.Date => ("date", DateFormat, "date"),
InputDateType.DateTimeLocal => ("datetime-local", DateTimeLocalFormat, "date and time"),
InputDateType.Month => ("month", MonthFormat, "year and month"),
InputDateType.Time => ("time", TimeFormat, "time"),
- _ => throw new InvalidOperationException($"Unsupported {nameof(InputDateType)} '{Type}'.")
+ _ => throw new InvalidOperationException($"Unsupported {nameof(InputDateType)} '{effectiveType}'.")
};
_parsingErrorMessage = string.IsNullOrEmpty(ParsingErrorMessage)
@@ -80,6 +88,18 @@ protected override void OnParametersSet()
: ParsingErrorMessage;
}
+ private static InputDateType GetDefaultInputDateType()
+ {
+ var type = Nullable.GetUnderlyingType(typeof(TValue)) ?? typeof(TValue);
+
+ return type switch
+ {
+ Type t when t == typeof(DateOnly) => InputDateType.Date,
+ Type t when t == typeof(TimeOnly) => InputDateType.Time,
+ _ => InputDateType.DateTimeLocal, // DateTime and DateTimeOffset
+ };
+ }
+
///
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
diff --git a/src/Components/Web/src/PublicAPI.Unshipped.txt b/src/Components/Web/src/PublicAPI.Unshipped.txt
index 369f33715778..69eb3a7a5d7c 100644
--- a/src/Components/Web/src/PublicAPI.Unshipped.txt
+++ b/src/Components/Web/src/PublicAPI.Unshipped.txt
@@ -40,3 +40,5 @@ Microsoft.AspNetCore.Components.Web.Media.MediaSource.MediaSource(byte[]! data,
Microsoft.AspNetCore.Components.Web.Media.MediaSource.MediaSource(System.IO.Stream! stream, string! mimeType, string! cacheKey) -> void
Microsoft.AspNetCore.Components.Web.Media.MediaSource.MimeType.get -> string!
Microsoft.AspNetCore.Components.Web.Media.MediaSource.Stream.get -> System.IO.Stream!
+*REMOVED*Microsoft.AspNetCore.Components.Forms.InputDate.Type.get -> Microsoft.AspNetCore.Components.Forms.InputDateType
+Microsoft.AspNetCore.Components.Forms.InputDate.Type.get -> Microsoft.AspNetCore.Components.Forms.InputDateType?
diff --git a/src/Components/Web/test/Forms/InputDateTest.cs b/src/Components/Web/test/Forms/InputDateTest.cs
index a698e550be1a..95ccf51a3cdc 100644
--- a/src/Components/Web/test/Forms/InputDateTest.cs
+++ b/src/Components/Web/test/Forms/InputDateTest.cs
@@ -26,9 +26,10 @@ public async Task ValidationErrorUsesDisplayAttributeName()
await inputComponent.SetCurrentValueAsStringAsync("invalidDate");
// Assert
+ // DateTime defaults to DateTimeLocal, so the error message is "date and time"
var validationMessages = rootComponent.EditContext.GetValidationMessages(fieldIdentifier);
Assert.NotEmpty(validationMessages);
- Assert.Contains("The Date property field must be a date.", validationMessages);
+ Assert.Contains("The Date property field must be a date and time.", validationMessages);
}
[Fact]
@@ -49,11 +50,168 @@ public async Task InputElementIsAssignedSuccessfully()
Assert.NotNull(inputSelectComponent.Element);
}
+ [Fact]
+ public async Task DateTimeDefaultsToDateTimeLocal()
+ {
+ // Arrange
+ var model = new TestModel();
+ var rootComponent = new TestInputHostComponent
+ {
+ EditContext = new EditContext(model),
+ ValueExpression = () => model.DateProperty,
+ };
+
+ // Act
+ var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
+
+ // Assert - DateTime should default to DateTimeLocal, not explicitly set Type
+ Assert.Null(inputComponent.Type);
+ }
+
+ [Fact]
+ public async Task DateTimeOffsetDefaultsToDateTimeLocal()
+ {
+ // Arrange
+ var model = new TestModelDateTimeOffset();
+ var rootComponent = new TestInputHostComponent
+ {
+ EditContext = new EditContext(model),
+ ValueExpression = () => model.DateProperty,
+ };
+
+ // Act
+ var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
+
+ // Assert - DateTimeOffset should default to DateTimeLocal
+ Assert.Null(inputComponent.Type);
+ }
+
+ [Fact]
+ public async Task DateOnlyDefaultsToDate()
+ {
+ // Arrange
+ var model = new TestModelDateOnly();
+ var rootComponent = new TestInputHostComponent
+ {
+ EditContext = new EditContext(model),
+ ValueExpression = () => model.DateProperty,
+ };
+
+ // Act
+ var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
+
+ // Assert - DateOnly should default to Date
+ Assert.Null(inputComponent.Type);
+ }
+
+ [Fact]
+ public async Task TimeOnlyDefaultsToTime()
+ {
+ // Arrange
+ var model = new TestModelTimeOnly();
+ var rootComponent = new TestInputHostComponent
+ {
+ EditContext = new EditContext(model),
+ ValueExpression = () => model.TimeProperty,
+ };
+
+ // Act
+ var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
+
+ // Assert - TimeOnly should default to Time
+ Assert.Null(inputComponent.Type);
+ }
+
+ [Fact]
+ public async Task ExplicitTypeOverridesAutoDetection()
+ {
+ // Arrange
+ var model = new TestModel();
+ var rootComponent = new TestInputHostComponent
+ {
+ EditContext = new EditContext(model),
+ ValueExpression = () => model.DateProperty,
+ AdditionalAttributes = new Dictionary
+ {
+ { "Type", InputDateType.Date }
+ }
+ };
+ var fieldIdentifier = FieldIdentifier.Create(() => model.DateProperty);
+ var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
+
+ // Act
+ await inputComponent.SetCurrentValueAsStringAsync("invalidDate");
+
+ // Assert - Explicitly set Type=Date should produce "date" error message
+ var validationMessages = rootComponent.EditContext.GetValidationMessages(fieldIdentifier);
+ Assert.NotEmpty(validationMessages);
+ Assert.Contains("The DateProperty field must be a date.", validationMessages);
+ }
+
+ [Fact]
+ public async Task TimeOnlyValidationErrorMessage()
+ {
+ // Arrange
+ var model = new TestModelTimeOnly();
+ var rootComponent = new TestInputHostComponent
+ {
+ EditContext = new EditContext(model),
+ ValueExpression = () => model.TimeProperty,
+ };
+ var fieldIdentifier = FieldIdentifier.Create(() => model.TimeProperty);
+ var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
+
+ // Act
+ await inputComponent.SetCurrentValueAsStringAsync("invalidTime");
+
+ // Assert - TimeOnly should default to Time, so error message is "time"
+ var validationMessages = rootComponent.EditContext.GetValidationMessages(fieldIdentifier);
+ Assert.NotEmpty(validationMessages);
+ Assert.Contains("The TimeProperty field must be a time.", validationMessages);
+ }
+
+ [Fact]
+ public async Task DateOnlyValidationErrorMessage()
+ {
+ // Arrange
+ var model = new TestModelDateOnly();
+ var rootComponent = new TestInputHostComponent
+ {
+ EditContext = new EditContext(model),
+ ValueExpression = () => model.DateProperty,
+ };
+ var fieldIdentifier = FieldIdentifier.Create(() => model.DateProperty);
+ var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
+
+ // Act
+ await inputComponent.SetCurrentValueAsStringAsync("invalidDate");
+
+ // Assert - DateOnly should default to Date, so error message is "date"
+ var validationMessages = rootComponent.EditContext.GetValidationMessages(fieldIdentifier);
+ Assert.NotEmpty(validationMessages);
+ Assert.Contains("The DateProperty field must be a date.", validationMessages);
+ }
+
private class TestModel
{
public DateTime DateProperty { get; set; }
}
+ private class TestModelDateTimeOffset
+ {
+ public DateTimeOffset DateProperty { get; set; }
+ }
+
+ private class TestModelDateOnly
+ {
+ public DateOnly DateProperty { get; set; }
+ }
+
+ private class TestModelTimeOnly
+ {
+ public TimeOnly TimeProperty { get; set; }
+ }
+
private class TestInputDateComponent : InputDate
{
public async Task SetCurrentValueAsStringAsync(string value)
@@ -65,4 +223,28 @@ public async Task SetCurrentValueAsStringAsync(string value)
await InvokeAsync(() => { base.CurrentValueAsString = value; });
}
}
+
+ private class TestInputDateTimeOffsetComponent : InputDate
+ {
+ public async Task SetCurrentValueAsStringAsync(string value)
+ {
+ await InvokeAsync(() => { base.CurrentValueAsString = value; });
+ }
+ }
+
+ private class TestInputDateOnlyComponent : InputDate
+ {
+ public async Task SetCurrentValueAsStringAsync(string value)
+ {
+ await InvokeAsync(() => { base.CurrentValueAsString = value; });
+ }
+ }
+
+ private class TestInputTimeOnlyComponent : InputDate
+ {
+ public async Task SetCurrentValueAsStringAsync(string value)
+ {
+ await InvokeAsync(() => { base.CurrentValueAsString = value; });
+ }
+ }
}
From ade5aeda89ae488a0f864bc87d009e2199aaba37 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 3 Dec 2025 09:58:02 +0000
Subject: [PATCH 3/4] Simplify GetDefaultInputDateType method based on code
review feedback
Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com>
---
src/Components/Web/src/Forms/InputDate.cs | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/src/Components/Web/src/Forms/InputDate.cs b/src/Components/Web/src/Forms/InputDate.cs
index 131aebb06b8d..87a3fd347131 100644
--- a/src/Components/Web/src/Forms/InputDate.cs
+++ b/src/Components/Web/src/Forms/InputDate.cs
@@ -92,12 +92,17 @@ private static InputDateType GetDefaultInputDateType()
{
var type = Nullable.GetUnderlyingType(typeof(TValue)) ?? typeof(TValue);
- return type switch
+ if (type == typeof(DateOnly))
{
- Type t when t == typeof(DateOnly) => InputDateType.Date,
- Type t when t == typeof(TimeOnly) => InputDateType.Time,
- _ => InputDateType.DateTimeLocal, // DateTime and DateTimeOffset
- };
+ return InputDateType.Date;
+ }
+
+ if (type == typeof(TimeOnly))
+ {
+ return InputDateType.Time;
+ }
+
+ return InputDateType.DateTimeLocal; // DateTime and DateTimeOffset
}
///
From c691d7feebc3fc8bba10efca025e83dd5f5b6999 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 3 Dec 2025 10:01:57 +0000
Subject: [PATCH 4/4] Update to conservative implementation - auto-detect
TimeOnly only
Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com>
---
src/Components/Web/src/Forms/InputDate.cs | 11 +++-------
.../Web/test/Forms/InputDateTest.cs | 22 +++++++++----------
2 files changed, 14 insertions(+), 19 deletions(-)
diff --git a/src/Components/Web/src/Forms/InputDate.cs b/src/Components/Web/src/Forms/InputDate.cs
index 87a3fd347131..9fb60b07ec63 100644
--- a/src/Components/Web/src/Forms/InputDate.cs
+++ b/src/Components/Web/src/Forms/InputDate.cs
@@ -33,9 +33,8 @@ public class InputDate<[DynamicallyAccessedMembers(DynamicallyAccessedMemberType
/// Gets or sets the type of HTML input to be rendered.
/// If not specified, the type is automatically inferred based on :
///
- /// - and default to
- /// - defaults to
/// - defaults to
+ /// - All other types (, , ) default to
///
///
[Parameter] public InputDateType? Type { get; set; }
@@ -92,17 +91,13 @@ private static InputDateType GetDefaultInputDateType()
{
var type = Nullable.GetUnderlyingType(typeof(TValue)) ?? typeof(TValue);
- if (type == typeof(DateOnly))
- {
- return InputDateType.Date;
- }
-
if (type == typeof(TimeOnly))
{
return InputDateType.Time;
}
- return InputDateType.DateTimeLocal; // DateTime and DateTimeOffset
+ // DateTime, DateTimeOffset, and DateOnly all default to Date for backward compatibility
+ return InputDateType.Date;
}
///
diff --git a/src/Components/Web/test/Forms/InputDateTest.cs b/src/Components/Web/test/Forms/InputDateTest.cs
index 95ccf51a3cdc..b37a8411c082 100644
--- a/src/Components/Web/test/Forms/InputDateTest.cs
+++ b/src/Components/Web/test/Forms/InputDateTest.cs
@@ -26,10 +26,10 @@ public async Task ValidationErrorUsesDisplayAttributeName()
await inputComponent.SetCurrentValueAsStringAsync("invalidDate");
// Assert
- // DateTime defaults to DateTimeLocal, so the error message is "date and time"
+ // DateTime defaults to Date for backward compatibility
var validationMessages = rootComponent.EditContext.GetValidationMessages(fieldIdentifier);
Assert.NotEmpty(validationMessages);
- Assert.Contains("The Date property field must be a date and time.", validationMessages);
+ Assert.Contains("The Date property field must be a date.", validationMessages);
}
[Fact]
@@ -51,7 +51,7 @@ public async Task InputElementIsAssignedSuccessfully()
}
[Fact]
- public async Task DateTimeDefaultsToDateTimeLocal()
+ public async Task DateTimeDefaultsToDate()
{
// Arrange
var model = new TestModel();
@@ -64,12 +64,12 @@ public async Task DateTimeDefaultsToDateTimeLocal()
// Act
var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
- // Assert - DateTime should default to DateTimeLocal, not explicitly set Type
+ // Assert - DateTime should default to Date (Type is null, auto-detected)
Assert.Null(inputComponent.Type);
}
[Fact]
- public async Task DateTimeOffsetDefaultsToDateTimeLocal()
+ public async Task DateTimeOffsetDefaultsToDate()
{
// Arrange
var model = new TestModelDateTimeOffset();
@@ -82,7 +82,7 @@ public async Task DateTimeOffsetDefaultsToDateTimeLocal()
// Act
var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
- // Assert - DateTimeOffset should default to DateTimeLocal
+ // Assert - DateTimeOffset should default to Date (Type is null, auto-detected)
Assert.Null(inputComponent.Type);
}
@@ -100,7 +100,7 @@ public async Task DateOnlyDefaultsToDate()
// Act
var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
- // Assert - DateOnly should default to Date
+ // Assert - DateOnly should default to Date (Type is null, auto-detected)
Assert.Null(inputComponent.Type);
}
@@ -118,7 +118,7 @@ public async Task TimeOnlyDefaultsToTime()
// Act
var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
- // Assert - TimeOnly should default to Time
+ // Assert - TimeOnly should default to Time (Type is null, auto-detected)
Assert.Null(inputComponent.Type);
}
@@ -133,7 +133,7 @@ public async Task ExplicitTypeOverridesAutoDetection()
ValueExpression = () => model.DateProperty,
AdditionalAttributes = new Dictionary
{
- { "Type", InputDateType.Date }
+ { "Type", InputDateType.DateTimeLocal }
}
};
var fieldIdentifier = FieldIdentifier.Create(() => model.DateProperty);
@@ -142,10 +142,10 @@ public async Task ExplicitTypeOverridesAutoDetection()
// Act
await inputComponent.SetCurrentValueAsStringAsync("invalidDate");
- // Assert - Explicitly set Type=Date should produce "date" error message
+ // Assert - Explicitly set Type=DateTimeLocal should produce "date and time" error message
var validationMessages = rootComponent.EditContext.GetValidationMessages(fieldIdentifier);
Assert.NotEmpty(validationMessages);
- Assert.Contains("The DateProperty field must be a date.", validationMessages);
+ Assert.Contains("The DateProperty field must be a date and time.", validationMessages);
}
[Fact]