diff --git a/src/Components/Web/src/Forms/InputDate.cs b/src/Components/Web/src/Forms/InputDate.cs
index 4498dd539b48..9fb60b07ec63 100644
--- a/src/Components/Web/src/Forms/InputDate.cs
+++ b/src/Components/Web/src/Forms/InputDate.cs
@@ -31,8 +31,13 @@ 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 :
+ ///
+ /// - defaults to
+ /// - All other types (, , ) default 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 +71,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 +87,19 @@ protected override void OnParametersSet()
: ParsingErrorMessage;
}
+ private static InputDateType GetDefaultInputDateType()
+ {
+ var type = Nullable.GetUnderlyingType(typeof(TValue)) ?? typeof(TValue);
+
+ if (type == typeof(TimeOnly))
+ {
+ return InputDateType.Time;
+ }
+
+ // DateTime, DateTimeOffset, and DateOnly all default to Date for backward compatibility
+ return InputDateType.Date;
+ }
+
///
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..b37a8411c082 100644
--- a/src/Components/Web/test/Forms/InputDateTest.cs
+++ b/src/Components/Web/test/Forms/InputDateTest.cs
@@ -26,6 +26,7 @@ public async Task ValidationErrorUsesDisplayAttributeName()
await inputComponent.SetCurrentValueAsStringAsync("invalidDate");
// Assert
+ // 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.", validationMessages);
@@ -49,11 +50,168 @@ public async Task InputElementIsAssignedSuccessfully()
Assert.NotNull(inputSelectComponent.Element);
}
+ [Fact]
+ public async Task DateTimeDefaultsToDate()
+ {
+ // 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 Date (Type is null, auto-detected)
+ Assert.Null(inputComponent.Type);
+ }
+
+ [Fact]
+ public async Task DateTimeOffsetDefaultsToDate()
+ {
+ // 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 Date (Type is null, auto-detected)
+ 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 (Type is null, auto-detected)
+ 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 (Type is null, auto-detected)
+ 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.DateTimeLocal }
+ }
+ };
+ var fieldIdentifier = FieldIdentifier.Create(() => model.DateProperty);
+ var inputComponent = await InputRenderer.RenderAndGetComponent(rootComponent);
+
+ // Act
+ await inputComponent.SetCurrentValueAsStringAsync("invalidDate");
+
+ // 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 and time.", 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; });
+ }
+ }
}