From a8095a9c792f6f927ebc59bdf273fb21c2eb624b Mon Sep 17 00:00:00 2001 From: Konkolyi Tibor <57896209+w3ori@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:22:15 +0200 Subject: [PATCH 01/22] Add GetDateTimeOrNull behavior --- .../DateTimePicker/MudBaseDatePickerX.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs index f6b9c008..bef1c88a 100644 --- a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs +++ b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs @@ -107,7 +107,7 @@ protected MudBaseDatePickerX() /// The current month shown in the date picker. /// /// - /// Defaults to the current month.
+ /// Defaults to the current month. /// When bound via @bind-PickerMonth, controls the initial month displayed. This value is always the first day of a month. ///
[Parameter] @@ -133,7 +133,7 @@ public DateTime? PickerMonth /// The delay, in milliseconds, before closing the picker after a value is selected. /// /// - /// Defaults to 100.
+ /// Defaults to 100. /// This delay helps the user see that a date has been selected before the popover disappears. ///
[Parameter] public int ClosingDelay { get; set; } = 100; @@ -150,7 +150,7 @@ public DateTime? PickerMonth /// The maximum number of months allowed in one row. /// /// - /// Defaults to null.
+ /// Defaults to null. /// When null, the is used. ///
[Parameter] public int? MaxMonthColumns { get; set; } @@ -172,7 +172,7 @@ public DateTime? PickerMonth /// The format of the selected date in the title. /// /// - /// Defaults to ddd, dd MMM.
+ /// Defaults to ddd, dd MMM. /// Supported date formats can be found here: . ///
[Parameter] public string TitleDateFormat { get; set; } = "ddd, dd MMM"; @@ -189,7 +189,7 @@ public DateTime? PickerMonth /// The function used to disable one or more dates. /// /// - /// Defaults to null.
+ /// Defaults to null. /// When set, a date will be disabled if the function returns true. ///
[Parameter] public Func IsDateDisabledFunc { get; set; } = _ => false; @@ -261,13 +261,13 @@ public DateTime? PickerMonth var tz = TimeZone ?? TimeZoneInfo.Local; if (value is DateTime dt) - return dt; + return dt == DateTime.MinValue ? null : dt; if (value is DateTimeOffset dto) - return TimeZoneInfo.ConvertTime(dto, tz).DateTime; + return dto == DateTimeOffset.MinValue ? null : TimeZoneInfo.ConvertTime(dto, tz).DateTime; if (value is DateOnly d) - return d.ToDateTime(TimeOnly.MinValue); + return d == DateOnly.MinValue ? null : d.ToDateTime(TimeOnly.MinValue); throw new NotSupportedException($"Type {typeof(T)} not supported"); } From 9174ad642c7b58a71861276e6ec6dfd46676366b Mon Sep 17 00:00:00 2001 From: Konkolyi Tibor <57896209+w3ori@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:22:43 +0200 Subject: [PATCH 02/22] Update DateTimePicker docs/examples for more generic types --- .../DateTimePicker/DateTimePickerPage.razor | 2 +- .../Examples/DateTimePickerExample2.razor | 26 ++++-- .../Examples/DateTimePickerExample3.razor | 91 ++++++++++++++++--- 3 files changed, 99 insertions(+), 20 deletions(-) diff --git a/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/DateTimePickerPage.razor b/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/DateTimePickerPage.razor index a2daa74d..abe7efff 100644 --- a/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/DateTimePickerPage.razor +++ b/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/DateTimePickerPage.razor @@ -5,7 +5,7 @@ - + diff --git a/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor b/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor index e599abe7..a1d49353 100644 --- a/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor +++ b/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor @@ -3,11 +3,16 @@ - - - - + + + + + + + + + @@ -19,9 +24,14 @@ @code { - private DateTime? _date = DateTime.Now; - private DateTimeOffset _date2 = DateTimeOffset.UtcNow; - private DateOnly _date3 = DateOnly.FromDateTime(DateTime.Now); + private DateTime _dateTime = DateTime.Now; + private DateTime? _nullableDateTime; + private DateTimeOffset _dateTimeOffset = DateTimeOffset.UtcNow; + private DateTimeOffset? _nullableDateTimeOffset; + private DateOnly _dateOnly = DateOnly.FromDateTime(DateTime.Now); + private DateOnly? _nullableDateOnly; private bool _clearable = false; private bool _editable = false; } + + \ No newline at end of file diff --git a/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample3.razor b/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample3.razor index 2e0ca6d7..8e78683d 100644 --- a/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample3.razor +++ b/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample3.razor @@ -3,14 +3,61 @@ - - - Clear - Cancel - OK - - + + + + Clear + Cancel + OK + + + + + + Clear + Cancel + OK + + + + + + Clear + Cancel + OK + + + + + + Clear + Cancel + OK + + + + + + Clear + Cancel + OK + + + + + + Clear + Cancel + OK + + + @@ -32,15 +79,25 @@ } - Set Today + Set Current Values @code { - private MudDateTimePicker _picker = null!; + private MudDateTimePicker _dateTimePicker = null!; + private MudDateTimePicker _nullableDateTimePicker = null!; + private MudDateTimePicker _dateTimeOffsetPicker = null!; + private MudDateTimePicker _nullableDateTimeOffsetPicker = null!; + private MudDateTimePicker _dateOnlyPicker = null!; + private MudDateTimePicker _nullableDateOnlyPicker = null!; - private DateTime? _date = DateTime.Now; + private DateTime _dateTime = DateTime.Now; + private DateTime? _nullableDateTime; + private DateTimeOffset _dateTimeOffset = DateTimeOffset.UtcNow; + private DateTimeOffset? _nullableDateTimeOffset; + private DateOnly _dateOnly = DateOnly.FromDateTime(DateTime.Now); + private DateOnly? _nullableDateOnly; private bool _editable = false; private bool _showToolbar = true; private bool _submitOnClose = true; @@ -49,4 +106,16 @@ private bool _isAdornmentEnd = true; private bool _clearable = false; private Variant _variant = Variant.Outlined; + + private void SetCurrentValues() + { + _dateTime = DateTime.Now; + _nullableDateTime = DateTime.Now; + _dateTimeOffset = DateTimeOffset.UtcNow; + _nullableDateTimeOffset = DateTimeOffset.UtcNow; + _dateOnly = DateOnly.FromDateTime(DateTime.Now); + _nullableDateOnly = DateOnly.FromDateTime(DateTime.Now); + } } + + \ No newline at end of file From 414ab17d14695e5c7063ea9fcf6c0369cac9689b Mon Sep 17 00:00:00 2001 From: Konkolyi Tibor <57896209+w3ori@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:22:54 +0200 Subject: [PATCH 03/22] Expand DateTimePickerTests for more input scenarios --- .../Components/DateTimePickerTests.cs | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs b/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs index 0fec0d9d..b6e1c789 100644 --- a/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs +++ b/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs @@ -43,6 +43,32 @@ public void DateTimePicker_Should_Format_Correctly() text.Should().Be("03.05.2026 14:30"); } + [Test] + public void DateTimePicker_DateTime_Should_Render_Time() + { + var comp = RenderPicker( + value: new DateTime(2026, 5, 3, 14, 30, 0), + format: "dd.MM.yyyy HH:mm"); + + comp.Instance.ConvertSetInternal(comp.Instance.Value).Should().Be("03.05.2026 14:30"); + } + + [Test] + public void DateTimePicker_DateTimeNullable_Should_Render_Empty_When_Null() + { + var comp = RenderPicker(); + + comp.Find("input").GetAttribute("value").Should().BeNullOrEmpty(); + } + + [Test] + public void DateTimePicker_Default_DateTime_Should_Render_Empty() + { + var comp = RenderPicker(); + + comp.Find("input").GetAttribute("value").Should().BeNullOrEmpty(); + } + [Test] public void DateTimePicker_DateOnly_Should_Not_Render_Time() { @@ -53,8 +79,49 @@ public void DateTimePicker_DateOnly_Should_Not_Render_Time() comp.Markup.Should().NotContain("mud-picker-time"); } + [Test] + public void DateTimePicker_DateOnly_Should_Render_Date_Only() + { + var comp = RenderPicker( + value: new DateOnly(2026, 5, 3), + format: "dd.MM.yyyy"); + + comp.Instance.ConvertSetInternal(comp.Instance.Value).Should().Be("03.05.2026"); + } + + [Test] + public void DateTimePicker_Default_DateOnly_Should_Render_Empty() + { + var comp = RenderPicker(); + + comp.Find("input").GetAttribute("value").Should().BeNullOrEmpty(); + } + + [Test] + public void DateTimePicker_DateOnlyNullable_Should_Render_Empty_When_Null() + { + var comp = RenderPicker(); + + comp.Find("input").GetAttribute("value").Should().BeNullOrEmpty(); + } + [Test] public void DateTimePicker_DateTimeOffset_Should_Convert_Correctly() + { + DateTimeOffset value = new DateTimeOffset(2026, 5, 3, 10, 0, 0, TimeSpan.Zero); + + var comp = Context.Render>(p => p + .Add(x => x.Value, value) + .Add(x => x.TimeZone, TimeZoneInfo.Utc) + ); + + var dt = comp.Instance.ToDateTime(value); + + dt.Should().Be(new DateTime(2026, 5, 3, 10, 0, 0)); + } + + [Test] + public void DateTimePicker_DateTimeOffsetNullable_Should_Convert_Correctly() { DateTimeOffset? value = new DateTimeOffset(2026, 5, 3, 10, 0, 0, TimeSpan.Zero); @@ -68,6 +135,22 @@ public void DateTimePicker_DateTimeOffset_Should_Convert_Correctly() dt.Should().Be(new DateTime(2026, 5, 3, 10, 0, 0)); } + [Test] + public void DateTimePicker_DateTimeOffsetNullable_Should_Render_Empty_When_Null() + { + var comp = RenderPicker(); + + comp.Find("input").GetAttribute("value").Should().BeNullOrEmpty(); + } + + [Test] + public void DateTimePicker_Default_DateTimeOffset_Should_Render_Empty() + { + var comp = RenderPicker(); + + comp.Find("input").GetAttribute("value").Should().BeNullOrEmpty(); + } + [Test] public async Task DateTimePicker_Input_Change_Should_Update_Value() { From e395586ace7ef57ee9c17182c4826caa9dc71683 Mon Sep 17 00:00:00 2001 From: w3ori <57896209+w3ori@users.noreply.github.com> Date: Mon, 14 Sep 2026 08:29:55 +0200 Subject: [PATCH 04/22] Add ValueOnClear and update ClearAsync behavior Introduce a new parameter ValueOnClear (T?) on MudBaseDatePickerX to allow specifying the value applied when the clear button is used (defaults to default(T), null for nullable types). Update MudDateTimePicker.ClearAsync to call SetDateAsync(ToDateTime(ValueOnClear), true) instead of passing null, and change the close logic to honor the explicit close parameter (if AutoClose || close). This lets consumers control the cleared value and whether the picker closes when clearing. --- .../Components/DateTimePicker/MudBaseDatePickerX.cs | 10 ++++++++++ .../DateTimePicker/MudDateTimePicker.razor.cs | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs index bef1c88a..0f39191a 100644 --- a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs +++ b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs @@ -185,6 +185,16 @@ public DateTime? PickerMonth /// [Parameter] public bool AutoClose { get; set; } + /// + /// The value to set when the clear button is clicked. + /// + /// + /// Defaults to default.
+ /// For nullable types, this will default to null. For non-nullable types, this will default to the minimum value. + ///
+ [Parameter] + public T? ValueOnClear { get; set; } = default; + /// /// The function used to disable one or more dates. /// diff --git a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs index d8049512..f681e774 100644 --- a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs +++ b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs @@ -405,9 +405,9 @@ protected override async Task SubmitAsync() /// A task that represents the asynchronous operation. public override async Task ClearAsync(bool close = true) { - await SetDateAsync(null, true); + await SetDateAsync(ToDateTime(ValueOnClear), true); - if (AutoClose) + if (AutoClose || close) await CloseAsync(false); } From 8250947ed7e95292af6db04a102e1113992d6bff Mon Sep 17 00:00:00 2001 From: w3ori <57896209+w3ori@users.noreply.github.com> Date: Mon, 14 Sep 2026 08:33:18 +0200 Subject: [PATCH 05/22] Preserve min date values in picker Reset file --- .../Components/DateTimePicker/MudBaseDatePickerX.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs index 0f39191a..f4e08194 100644 --- a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs +++ b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs @@ -271,13 +271,13 @@ public DateTime? PickerMonth var tz = TimeZone ?? TimeZoneInfo.Local; if (value is DateTime dt) - return dt == DateTime.MinValue ? null : dt; + return dt; if (value is DateTimeOffset dto) - return dto == DateTimeOffset.MinValue ? null : TimeZoneInfo.ConvertTime(dto, tz).DateTime; + return TimeZoneInfo.ConvertTime(dto, tz).DateTime; if (value is DateOnly d) - return d == DateOnly.MinValue ? null : d.ToDateTime(TimeOnly.MinValue); + return d.ToDateTime(TimeOnly.MinValue); throw new NotSupportedException($"Type {typeof(T)} not supported"); } From 806646f8a5cac7ae8534114a551f01db55f51e87 Mon Sep 17 00:00:00 2001 From: w3ori <57896209+w3ori@users.noreply.github.com> Date: Wed, 16 Sep 2026 08:06:52 +0200 Subject: [PATCH 06/22] Add ValueOnClear docs and DateTimePicker tests --- .../wwwroot/CodeBeam.MudBlazor.Extensions.xml | 19 ++- .../Components/DateTimePickerTests.cs | 126 ++++++++++++++++++ 2 files changed, 140 insertions(+), 5 deletions(-) diff --git a/docs/CodeBeam.MudBlazor.Extensions.Docs.Wasm/wwwroot/CodeBeam.MudBlazor.Extensions.xml b/docs/CodeBeam.MudBlazor.Extensions.Docs.Wasm/wwwroot/CodeBeam.MudBlazor.Extensions.xml index c79cee8a..739e3c66 100644 --- a/docs/CodeBeam.MudBlazor.Extensions.Docs.Wasm/wwwroot/CodeBeam.MudBlazor.Extensions.xml +++ b/docs/CodeBeam.MudBlazor.Extensions.Docs.Wasm/wwwroot/CodeBeam.MudBlazor.Extensions.xml @@ -1387,7 +1387,7 @@ The current month shown in the date picker. - Defaults to the current month.
+ Defaults to the current month. When bound via @bind-PickerMonth, controls the initial month displayed. This value is always the first day of a month.
@@ -1401,7 +1401,7 @@ The delay, in milliseconds, before closing the picker after a value is selected. - Defaults to 100.
+ Defaults to 100. This delay helps the user see that a date has been selected before the popover disappears.
@@ -1418,7 +1418,7 @@ The maximum number of months allowed in one row. - Defaults to null.
+ Defaults to null. When null, the is used.
@@ -1440,7 +1440,7 @@ The format of the selected date in the title. - Defaults to ddd, dd MMM.
+ Defaults to ddd, dd MMM. Supported date formats can be found here: .
@@ -1452,12 +1452,21 @@ Defaults to false. + + + The value to set when the clear button is clicked. + + + Defaults to default.
+ For nullable types, this will default to null. For non-nullable types, this will default to the minimum value. +
+
The function used to disable one or more dates. - Defaults to null.
+ Defaults to null. When set, a date will be disabled if the function returns true.
diff --git a/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs b/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs index b6e1c789..6d6f037b 100644 --- a/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs +++ b/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs @@ -181,4 +181,130 @@ public void DateTimePicker_Null_Value_Should_Render_Empty() var comp = RenderPicker(); comp.Find("input").GetAttribute("value").Should().BeNullOrEmpty(); } + + [Test] + public async Task DateTimePicker_Clear_Without_ValueOnClear_Should_Set_Null_For_Nullable() + { + DateTime? value = new DateTime(2026, 5, 3, 14, 30, 0); + var callback = EventCallback.Factory.Create(this, v => value = v); + + var comp = RenderPicker( + value: value, + valueChanged: callback); + + await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); + + value.Should().BeNull(); + } + + [Test] + public async Task DateTimePicker_Clear_With_ValueOnClear_Should_Set_Custom_Value_Nullable() + { + DateTime? value = new DateTime(2026, 5, 3, 14, 30, 0); + var customClearValue = new DateTime(2025, 1, 1, 0, 0, 0); + var callback = EventCallback.Factory.Create(this, v => value = v); + + var comp = Context.Render>(parameters => + { + parameters.Add(p => p.Value, value); + parameters.Add(p => p.ValueChanged, callback); + parameters.Add(p => p.ValueOnClear, customClearValue); + }); + + await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); + + value.Should().Be(customClearValue); + } + + [Test] + public async Task DateTimePicker_Clear_With_ValueOnClear_DateOnly_Should_Set_Custom_Value() + { + DateOnly value = new DateOnly(2026, 5, 3); + var customClearValue = new DateOnly(2025, 1, 1); + var callback = EventCallback.Factory.Create(this, v => value = v); + + var comp = Context.Render>(parameters => + { + parameters.Add(p => p.Value, value); + parameters.Add(p => p.ValueChanged, callback); + parameters.Add(p => p.ValueOnClear, customClearValue); + }); + + await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); + + value.Should().Be(customClearValue); + } + + [Test] + public async Task DateTimePicker_Clear_Without_ValueOnClear_DateOnly_Should_Set_Default() + { + DateOnly value = new DateOnly(2026, 5, 3); + var callback = EventCallback.Factory.Create(this, v => value = v); + + var comp = Context.Render>(parameters => + { + parameters.Add(p => p.Value, value); + parameters.Add(p => p.ValueChanged, callback); + }); + + await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); + + value.Should().Be(default(DateOnly)); + } + + [Test] + public async Task DateTimePicker_Clear_With_ValueOnClear_DateTimeOffset_Should_Set_Custom_Value() + { + DateTimeOffset value = new DateTimeOffset(2026, 5, 3, 14, 30, 0, TimeSpan.Zero); + var customClearValue = new DateTimeOffset(2025, 1, 1, 10, 0, 0, TimeSpan.Zero); + var callback = EventCallback.Factory.Create(this, v => value = v); + + var comp = Context.Render>(parameters => + { + parameters.Add(p => p.Value, value); + parameters.Add(p => p.ValueChanged, callback); + parameters.Add(p => p.ValueOnClear, customClearValue); + parameters.Add(p => p.TimeZone, TimeZoneInfo.Utc); + }); + + await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); + + value.Should().Be(customClearValue); + } + + [Test] + public async Task DateTimePicker_Clear_Without_ValueOnClear_DateTimeOffset_Should_Set_Default() + { + DateTimeOffset value = new DateTimeOffset(2026, 5, 3, 14, 30, 0, TimeSpan.Zero); + var callback = EventCallback.Factory.Create(this, v => value = v); + + var comp = Context.Render>(parameters => + { + parameters.Add(p => p.Value, value); + parameters.Add(p => p.ValueChanged, callback); + parameters.Add(p => p.TimeZone, TimeZoneInfo.Utc); + }); + + await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); + + value.Should().Be(default(DateTimeOffset)); + } + + [Test] + public async Task DateTimePicker_Clear_With_ValueOnClear_Null_Nullable_Should_Set_Null() + { + DateTime? value = new DateTime(2026, 5, 3, 14, 30, 0); + var callback = EventCallback.Factory.Create(this, v => value = v); + + var comp = Context.Render>(parameters => + { + parameters.Add(p => p.Value, value); + parameters.Add(p => p.ValueChanged, callback); + parameters.Add(p => p.ValueOnClear, null); + }); + + await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); + + value.Should().BeNull(); + } } From 1ad8c46e574265825dce15f3561830e71d546771 Mon Sep 17 00:00:00 2001 From: w3ori <57896209+w3ori@users.noreply.github.com> Date: Wed, 16 Sep 2026 13:47:39 +0200 Subject: [PATCH 07/22] Fix empty-state date picker behavior --- .../wwwroot/CodeBeam.MudBlazor.Extensions.xml | 38 ++++++-- .../DateTimePicker/MudBaseDatePickerX.cs | 44 ++++----- .../DateTimePicker/MudDateTimePicker.razor.cs | 79 ++++++++++------ .../Components/DateTimePickerTests.cs | 93 ++++++------------- 4 files changed, 132 insertions(+), 122 deletions(-) diff --git a/docs/CodeBeam.MudBlazor.Extensions.Docs.Wasm/wwwroot/CodeBeam.MudBlazor.Extensions.xml b/docs/CodeBeam.MudBlazor.Extensions.Docs.Wasm/wwwroot/CodeBeam.MudBlazor.Extensions.xml index 739e3c66..a493cd8f 100644 --- a/docs/CodeBeam.MudBlazor.Extensions.Docs.Wasm/wwwroot/CodeBeam.MudBlazor.Extensions.xml +++ b/docs/CodeBeam.MudBlazor.Extensions.Docs.Wasm/wwwroot/CodeBeam.MudBlazor.Extensions.xml @@ -1452,15 +1452,6 @@ Defaults to false. - - - The value to set when the clear button is clicked. - - - Defaults to default.
- For nullable types, this will default to null. For non-nullable types, this will default to the minimum value. -
-
The function used to disable one or more dates. @@ -1531,6 +1522,18 @@ The converted DateTime value. Thrown when the type T is not supported. + + + Gets the effective representation of the specified value for picker state decisions. + + + The default implementation performs a direct conversion via . + Derived components can override this method to treat certain values as an empty picker state while leaving + the underlying conversion behavior unchanged. + + The value to interpret for picker state. + The effective for picker behavior, or when the picker should behave as empty. + Converts a nullable value to an instance of type , if supported. @@ -1581,6 +1584,23 @@ + + + Determines whether the specified value represents the empty picker state for a non-nullable date type. + + The value to evaluate. + when is non-nullable and equals its default value; otherwise, . + + + + + + + Gets the text representation for the specified value using the effective picker-state interpretation. + + The value to format. + The formatted text for the value, or when the picker should display an empty state. + diff --git a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs index f4e08194..e93e3a8c 100644 --- a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs +++ b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs @@ -185,16 +185,6 @@ public DateTime? PickerMonth /// [Parameter] public bool AutoClose { get; set; } - /// - /// The value to set when the clear button is clicked. - /// - /// - /// Defaults to default.
- /// For nullable types, this will default to null. For non-nullable types, this will default to the minimum value. - ///
- [Parameter] - public T? ValueOnClear { get; set; } = default; - /// /// The function used to disable one or more dates. /// @@ -282,6 +272,21 @@ public DateTime? PickerMonth throw new NotSupportedException($"Type {typeof(T)} not supported"); } + /// + /// Gets the effective representation of the specified value for picker state decisions. + /// + /// + /// The default implementation performs a direct conversion via . + /// Derived components can override this method to treat certain values as an empty picker state while leaving + /// the underlying conversion behavior unchanged. + /// + /// The value to interpret for picker state. + /// The effective for picker behavior, or when the picker should behave as empty. + protected virtual DateTime? GetEffectiveDateTime(T? value) + { + return ToDateTime(value); + } + /// /// Converts a nullable value to an instance of type , if supported. /// @@ -372,18 +377,15 @@ protected override async Task OnPickerOpenedAsync() { await base.OnPickerOpenedAsync(); - var dateTime = ToDateTime(_value); + var dateTime = GetEffectiveDateTime(_value) ?? GetCalendarStartOfMonth(); + var culture = GetCulture(); + var calendar = culture.Calendar; - if (dateTime.HasValue) - { - var culture = GetCulture(); - var calendar = culture.Calendar; - PickerMonth = new DateTime( - calendar.GetYear(dateTime.Value), - calendar.GetMonth(dateTime.Value), - 1, - calendar); - } + PickerMonth = new DateTime( + calendar.GetYear(dateTime), + calendar.GetMonth(dateTime), + 1, + calendar); CurrentView = OpenTo; } diff --git a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs index f681e774..41b1ac11 100644 --- a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs +++ b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs @@ -43,18 +43,38 @@ private record SetTime protected ElementReference ClockElementReference { get; private set; } private bool _amPm = false; + /// + /// Determines whether the specified value represents the empty picker state for a non-nullable date type. + /// + /// The value to evaluate. + /// when is non-nullable and equals its default value; otherwise, . + private static bool IsEmptyNonNullableValue(T? value) + => Nullable.GetUnderlyingType(typeof(T)) is null && EqualityComparer.Default.Equals(value, default); + + /// + protected override DateTime? GetEffectiveDateTime(T? value) + => IsEmptyNonNullableValue(value) ? null : ToDateTime(value); + + /// + /// Gets the text representation for the specified value using the effective picker-state interpretation. + /// + /// The value to format. + /// The formatted text for the value, or when the picker should display an empty state. + private string? GetEffectiveText(T? value) + => GetEffectiveDateTime(value) is null ? null : ConvertSet(value); + /// protected override void OnInitialized() { base.OnInitialized(); - _workingValue = ToDateTime(Value); + _workingValue = GetEffectiveDateTime(Value); } /// protected override void OnParametersSet() { base.OnParametersSet(); - _workingValue = ToDateTime(Value); + _workingValue = GetEffectiveDateTime(Value); SyncTimeFromValue(); } @@ -97,7 +117,7 @@ private void SyncTimeFromValue() public T? Value { get => _value; - set => SetDateAsync(ToDateTime(value), true).CatchAndLog(); + set => SetDateAsync(GetEffectiveDateTime(value), true).CatchAndLog(); } /// @@ -126,7 +146,7 @@ public bool AmPm _amPm = value; Touched = true; - _ = SetTextAsync(ConvertSet(_value), false); + _ = SetTextAsync(GetEffectiveText(_value), false); } } @@ -204,7 +224,7 @@ protected override async Task WriteTextAsync(string? text) } else { - await SetTextAsync(ConvertSet(_value), false); + await SetTextAsync(GetEffectiveText(_value), false); } } @@ -232,7 +252,7 @@ private async Task OnPmClickedAsync() protected internal async Task SetDateAsync(DateTime? date, bool updateValue) { - var current = ToDateTime(_value); + var current = GetEffectiveDateTime(_value); if (current != null && date != null && date.Value.Kind == DateTimeKind.Unspecified) { @@ -250,31 +270,32 @@ protected internal async Task SetDateAsync(DateTime? date, bool updateValue) { Touched = true; - HighlightedDate = date; - if (date is not null && IsDateDisabledFunc(date.Value.Date)) { await SetTextAsync(null, false); return; } - if (date is not null) + var converted = FromDateTime(date); + var effectiveDate = GetEffectiveDateTime(converted); + + HighlightedDate = effectiveDate; + _value = converted; + + if (effectiveDate is not null) { var culture = GetCulture(); PickerMonth = new DateTime( - culture.Calendar.GetYear(date.Value), - culture.Calendar.GetMonth(date.Value), + culture.Calendar.GetYear(effectiveDate.Value), + culture.Calendar.GetMonth(effectiveDate.Value), 1, culture.Calendar); } - var converted = FromDateTime(date); - _value = converted; - if (updateValue) { ResetConverterErrors(); - await SetTextAsync(ConvertSet(_value), false); + await SetTextAsync(GetEffectiveText(_value), false); } await ValueChanged.InvokeAsync(_value); @@ -335,7 +356,7 @@ protected override string GetDayClasses(int month, DateTime day) if (day < GetMonthStart(month) || day > GetMonthEnd(month)) return b.AddClass("mud-hidden").Build(); - var current = _workingValue ?? ToDateTime(Value); + var current = _workingValue ?? GetEffectiveDateTime(Value); if (current?.Date == day.Date) return b.AddClass("mud-selected") @@ -393,7 +414,7 @@ protected override async Task SubmitAsync() _value = converted; await ValueChanged.InvokeAsync(_value); - await SetTextAsync(ConvertSet(_value), false); + await SetTextAsync(GetEffectiveText(_value), false); await BeginValidateAsync(); FieldChanged(_value); } @@ -405,7 +426,7 @@ protected override async Task SubmitAsync() /// A task that represents the asynchronous operation. public override async Task ClearAsync(bool close = true) { - await SetDateAsync(ToDateTime(ValueOnClear), true); + await SetDateAsync(null, true); if (AutoClose || close) await CloseAsync(false); @@ -418,7 +439,7 @@ public override async Task ClearAsync(bool close = true) protected string GetTitleDateString() { var date = _workingValue - ?? ToDateTime(Value) + ?? GetEffectiveDateTime(Value) ?? TimeProvider.GetLocalNow().Date; return FormatTitleDate(date); @@ -433,7 +454,7 @@ protected string GetTitleDateString() /// or the current local date if neither is set. protected override DateTime GetCalendarStartOfMonth() { - var date = ToDateTime(Value) ?? HighlightedDate ?? TimeProvider.GetLocalNow().Date; + var date = GetEffectiveDateTime(Value) ?? HighlightedDate ?? TimeProvider.GetLocalNow().Date; return date.StartOfMonth(GetCulture()); } @@ -448,7 +469,7 @@ protected override DateTime GetCalendarStartOfMonth() /// The calendar year as an integer, adjusted based on the current value and the specified date. protected override int GetCalendarYear(DateTime yearDate) { - var date = ToDateTime(Value) ?? TimeProvider.GetLocalNow().Date; + var date = GetEffectiveDateTime(Value) ?? TimeProvider.GetLocalNow().Date; var diff = GetCulture().Calendar.GetYear(date) - GetCulture().Calendar.GetYear(yearDate); return GetCulture().Calendar.GetYear(date) - diff; @@ -501,7 +522,7 @@ protected int GetMaxYear() protected Task OnYearClickedAsync(int year) { - var current = ToDateTime(Value) ?? TimeProvider.GetLocalNow().Date; + var current = GetEffectiveDateTime(Value) ?? TimeProvider.GetLocalNow().Date; PickerMonth = new DateTime(year, current.Month, 1); _workingValue = new DateTime( @@ -519,13 +540,13 @@ protected Task OnYearClickedAsync(int year) protected Typo GetYearTypo(int year) { - var current = ToDateTime(Value); + var current = GetEffectiveDateTime(Value); return current?.Year == year ? Typo.h5 : Typo.body1; } protected string GetYearClasses(int year) { - var current = ToDateTime(Value); + var current = GetEffectiveDateTime(Value); return new CssBuilder("mud-picker-year-text") .AddClass("mud-selected", current?.Year == year) @@ -551,7 +572,7 @@ protected IEnumerable GetAllMonths() protected Task OnMonthSelectedAsync(int month) { - var current = _workingValue ?? ToDateTime(Value) ?? TimeProvider.GetLocalNow().Date; + var current = _workingValue ?? GetEffectiveDateTime(Value) ?? TimeProvider.GetLocalNow().Date; PickerMonth = new DateTime(current.Year, month, 1); _workingValue = new DateTime( @@ -583,13 +604,13 @@ protected bool IsMonthDisabled(int month) protected Typo GetMonthTypo(int month) { - var current = ToDateTime(Value); + var current = GetEffectiveDateTime(Value); return current?.Month == month ? Typo.h6 : Typo.body2; } protected string GetMonthClasses(int month) { - var current = ToDateTime(Value); + var current = GetEffectiveDateTime(Value); return new CssBuilder() .AddClass("mud-selected", current?.Month == month) @@ -874,7 +895,7 @@ private void HandleModeChange(PickerMode mode) protected string GetFormattedYearString() { var date = _workingValue - ?? ToDateTime(Value) + ?? GetEffectiveDateTime(Value) ?? TimeProvider.GetLocalNow().Date; return date.Year.ToString(); @@ -893,7 +914,7 @@ public override async Task ScrollToYearAsync(DateTime? date = null) var dateTime = date ?? _workingValue - ?? ToDateTime(Value) + ?? GetEffectiveDateTime(Value) ?? TimeProvider.GetLocalNow().Date; var id = $"{_componentId}{calendar.GetYear(dateTime)}"; diff --git a/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs b/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs index 6d6f037b..9db5616f 100644 --- a/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs +++ b/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs @@ -24,6 +24,21 @@ private IRenderedComponent> RenderPicker( }); } + private static async Task InvokePickerOpenedAsync(IRenderedComponent> comp) + { + var method = typeof(MudDateTimePicker).BaseType!.GetMethod( + "OnPickerOpenedAsync", + System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + + var task = (Task)method!.Invoke(comp.Instance, [])!; + await task; + } + + private static DateTime GetCurrentMonthStart() + { + return new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); + } + [Test] public void DateTimePicker_DefaultRender_Should_Render_Input() { @@ -105,6 +120,16 @@ public void DateTimePicker_DateOnlyNullable_Should_Render_Empty_When_Null() comp.Find("input").GetAttribute("value").Should().BeNullOrEmpty(); } + [Test] + public async Task DateTimePicker_Default_DateOnly_Should_Open_Current_Month() + { + var comp = RenderPicker(); + + await comp.InvokeAsync(() => InvokePickerOpenedAsync(comp)); + + comp.Instance.PickerMonth.Should().Be(GetCurrentMonthStart()); + } + [Test] public void DateTimePicker_DateTimeOffset_Should_Convert_Correctly() { @@ -198,45 +223,24 @@ public async Task DateTimePicker_Clear_Without_ValueOnClear_Should_Set_Null_For_ } [Test] - public async Task DateTimePicker_Clear_With_ValueOnClear_Should_Set_Custom_Value_Nullable() - { - DateTime? value = new DateTime(2026, 5, 3, 14, 30, 0); - var customClearValue = new DateTime(2025, 1, 1, 0, 0, 0); - var callback = EventCallback.Factory.Create(this, v => value = v); - - var comp = Context.Render>(parameters => - { - parameters.Add(p => p.Value, value); - parameters.Add(p => p.ValueChanged, callback); - parameters.Add(p => p.ValueOnClear, customClearValue); - }); - - await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); - - value.Should().Be(customClearValue); - } - - [Test] - public async Task DateTimePicker_Clear_With_ValueOnClear_DateOnly_Should_Set_Custom_Value() + public async Task DateTimePicker_Clear_Without_ValueOnClear_DateOnly_Should_Set_Default() { DateOnly value = new DateOnly(2026, 5, 3); - var customClearValue = new DateOnly(2025, 1, 1); var callback = EventCallback.Factory.Create(this, v => value = v); var comp = Context.Render>(parameters => { parameters.Add(p => p.Value, value); parameters.Add(p => p.ValueChanged, callback); - parameters.Add(p => p.ValueOnClear, customClearValue); }); await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); - value.Should().Be(customClearValue); + value.Should().Be(default(DateOnly)); } [Test] - public async Task DateTimePicker_Clear_Without_ValueOnClear_DateOnly_Should_Set_Default() + public async Task DateTimePicker_Clear_Without_ValueOnClear_DateOnly_Should_Open_Current_Month() { DateOnly value = new DateOnly(2026, 5, 3); var callback = EventCallback.Factory.Create(this, v => value = v); @@ -248,28 +252,9 @@ public async Task DateTimePicker_Clear_Without_ValueOnClear_DateOnly_Should_Set_ }); await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); + await comp.InvokeAsync(() => InvokePickerOpenedAsync(comp)); - value.Should().Be(default(DateOnly)); - } - - [Test] - public async Task DateTimePicker_Clear_With_ValueOnClear_DateTimeOffset_Should_Set_Custom_Value() - { - DateTimeOffset value = new DateTimeOffset(2026, 5, 3, 14, 30, 0, TimeSpan.Zero); - var customClearValue = new DateTimeOffset(2025, 1, 1, 10, 0, 0, TimeSpan.Zero); - var callback = EventCallback.Factory.Create(this, v => value = v); - - var comp = Context.Render>(parameters => - { - parameters.Add(p => p.Value, value); - parameters.Add(p => p.ValueChanged, callback); - parameters.Add(p => p.ValueOnClear, customClearValue); - parameters.Add(p => p.TimeZone, TimeZoneInfo.Utc); - }); - - await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); - - value.Should().Be(customClearValue); + comp.Instance.PickerMonth.Should().Be(GetCurrentMonthStart()); } [Test] @@ -289,22 +274,4 @@ public async Task DateTimePicker_Clear_Without_ValueOnClear_DateTimeOffset_Shoul value.Should().Be(default(DateTimeOffset)); } - - [Test] - public async Task DateTimePicker_Clear_With_ValueOnClear_Null_Nullable_Should_Set_Null() - { - DateTime? value = new DateTime(2026, 5, 3, 14, 30, 0); - var callback = EventCallback.Factory.Create(this, v => value = v); - - var comp = Context.Render>(parameters => - { - parameters.Add(p => p.Value, value); - parameters.Add(p => p.ValueChanged, callback); - parameters.Add(p => p.ValueOnClear, null); - }); - - await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); - - value.Should().BeNull(); - } } From 9c4adbca7a52230b8e2d04b6c22e66b4529bcb60 Mon Sep 17 00:00:00 2001 From: w3ori <57896209+w3ori@users.noreply.github.com> Date: Mon, 21 Sep 2026 08:07:41 +0200 Subject: [PATCH 08/22] Revert "Fix empty-state date picker behavior" This reverts commit 1ad8c46e574265825dce15f3561830e71d546771. --- .../wwwroot/CodeBeam.MudBlazor.Extensions.xml | 38 ++------ .../DateTimePicker/MudBaseDatePickerX.cs | 44 +++++---- .../DateTimePicker/MudDateTimePicker.razor.cs | 79 ++++++---------- .../Components/DateTimePickerTests.cs | 93 +++++++++++++------ 4 files changed, 122 insertions(+), 132 deletions(-) diff --git a/docs/CodeBeam.MudBlazor.Extensions.Docs.Wasm/wwwroot/CodeBeam.MudBlazor.Extensions.xml b/docs/CodeBeam.MudBlazor.Extensions.Docs.Wasm/wwwroot/CodeBeam.MudBlazor.Extensions.xml index a493cd8f..739e3c66 100644 --- a/docs/CodeBeam.MudBlazor.Extensions.Docs.Wasm/wwwroot/CodeBeam.MudBlazor.Extensions.xml +++ b/docs/CodeBeam.MudBlazor.Extensions.Docs.Wasm/wwwroot/CodeBeam.MudBlazor.Extensions.xml @@ -1452,6 +1452,15 @@ Defaults to false. + + + The value to set when the clear button is clicked. + + + Defaults to default.
+ For nullable types, this will default to null. For non-nullable types, this will default to the minimum value. +
+
The function used to disable one or more dates. @@ -1522,18 +1531,6 @@ The converted DateTime value. Thrown when the type T is not supported. - - - Gets the effective representation of the specified value for picker state decisions. - - - The default implementation performs a direct conversion via . - Derived components can override this method to treat certain values as an empty picker state while leaving - the underlying conversion behavior unchanged. - - The value to interpret for picker state. - The effective for picker behavior, or when the picker should behave as empty. - Converts a nullable value to an instance of type , if supported. @@ -1584,23 +1581,6 @@ - - - Determines whether the specified value represents the empty picker state for a non-nullable date type. - - The value to evaluate. - when is non-nullable and equals its default value; otherwise, . - - - - - - - Gets the text representation for the specified value using the effective picker-state interpretation. - - The value to format. - The formatted text for the value, or when the picker should display an empty state. - diff --git a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs index e93e3a8c..f4e08194 100644 --- a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs +++ b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs @@ -185,6 +185,16 @@ public DateTime? PickerMonth /// [Parameter] public bool AutoClose { get; set; } + /// + /// The value to set when the clear button is clicked. + /// + /// + /// Defaults to default.
+ /// For nullable types, this will default to null. For non-nullable types, this will default to the minimum value. + ///
+ [Parameter] + public T? ValueOnClear { get; set; } = default; + /// /// The function used to disable one or more dates. /// @@ -272,21 +282,6 @@ public DateTime? PickerMonth throw new NotSupportedException($"Type {typeof(T)} not supported"); } - /// - /// Gets the effective representation of the specified value for picker state decisions. - /// - /// - /// The default implementation performs a direct conversion via . - /// Derived components can override this method to treat certain values as an empty picker state while leaving - /// the underlying conversion behavior unchanged. - /// - /// The value to interpret for picker state. - /// The effective for picker behavior, or when the picker should behave as empty. - protected virtual DateTime? GetEffectiveDateTime(T? value) - { - return ToDateTime(value); - } - /// /// Converts a nullable value to an instance of type , if supported. /// @@ -377,15 +372,18 @@ protected override async Task OnPickerOpenedAsync() { await base.OnPickerOpenedAsync(); - var dateTime = GetEffectiveDateTime(_value) ?? GetCalendarStartOfMonth(); - var culture = GetCulture(); - var calendar = culture.Calendar; + var dateTime = ToDateTime(_value); - PickerMonth = new DateTime( - calendar.GetYear(dateTime), - calendar.GetMonth(dateTime), - 1, - calendar); + if (dateTime.HasValue) + { + var culture = GetCulture(); + var calendar = culture.Calendar; + PickerMonth = new DateTime( + calendar.GetYear(dateTime.Value), + calendar.GetMonth(dateTime.Value), + 1, + calendar); + } CurrentView = OpenTo; } diff --git a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs index 41b1ac11..f681e774 100644 --- a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs +++ b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs @@ -43,38 +43,18 @@ private record SetTime protected ElementReference ClockElementReference { get; private set; } private bool _amPm = false; - /// - /// Determines whether the specified value represents the empty picker state for a non-nullable date type. - /// - /// The value to evaluate. - /// when is non-nullable and equals its default value; otherwise, . - private static bool IsEmptyNonNullableValue(T? value) - => Nullable.GetUnderlyingType(typeof(T)) is null && EqualityComparer.Default.Equals(value, default); - - /// - protected override DateTime? GetEffectiveDateTime(T? value) - => IsEmptyNonNullableValue(value) ? null : ToDateTime(value); - - /// - /// Gets the text representation for the specified value using the effective picker-state interpretation. - /// - /// The value to format. - /// The formatted text for the value, or when the picker should display an empty state. - private string? GetEffectiveText(T? value) - => GetEffectiveDateTime(value) is null ? null : ConvertSet(value); - /// protected override void OnInitialized() { base.OnInitialized(); - _workingValue = GetEffectiveDateTime(Value); + _workingValue = ToDateTime(Value); } /// protected override void OnParametersSet() { base.OnParametersSet(); - _workingValue = GetEffectiveDateTime(Value); + _workingValue = ToDateTime(Value); SyncTimeFromValue(); } @@ -117,7 +97,7 @@ private void SyncTimeFromValue() public T? Value { get => _value; - set => SetDateAsync(GetEffectiveDateTime(value), true).CatchAndLog(); + set => SetDateAsync(ToDateTime(value), true).CatchAndLog(); } /// @@ -146,7 +126,7 @@ public bool AmPm _amPm = value; Touched = true; - _ = SetTextAsync(GetEffectiveText(_value), false); + _ = SetTextAsync(ConvertSet(_value), false); } } @@ -224,7 +204,7 @@ protected override async Task WriteTextAsync(string? text) } else { - await SetTextAsync(GetEffectiveText(_value), false); + await SetTextAsync(ConvertSet(_value), false); } } @@ -252,7 +232,7 @@ private async Task OnPmClickedAsync() protected internal async Task SetDateAsync(DateTime? date, bool updateValue) { - var current = GetEffectiveDateTime(_value); + var current = ToDateTime(_value); if (current != null && date != null && date.Value.Kind == DateTimeKind.Unspecified) { @@ -270,32 +250,31 @@ protected internal async Task SetDateAsync(DateTime? date, bool updateValue) { Touched = true; + HighlightedDate = date; + if (date is not null && IsDateDisabledFunc(date.Value.Date)) { await SetTextAsync(null, false); return; } - var converted = FromDateTime(date); - var effectiveDate = GetEffectiveDateTime(converted); - - HighlightedDate = effectiveDate; - _value = converted; - - if (effectiveDate is not null) + if (date is not null) { var culture = GetCulture(); PickerMonth = new DateTime( - culture.Calendar.GetYear(effectiveDate.Value), - culture.Calendar.GetMonth(effectiveDate.Value), + culture.Calendar.GetYear(date.Value), + culture.Calendar.GetMonth(date.Value), 1, culture.Calendar); } + var converted = FromDateTime(date); + _value = converted; + if (updateValue) { ResetConverterErrors(); - await SetTextAsync(GetEffectiveText(_value), false); + await SetTextAsync(ConvertSet(_value), false); } await ValueChanged.InvokeAsync(_value); @@ -356,7 +335,7 @@ protected override string GetDayClasses(int month, DateTime day) if (day < GetMonthStart(month) || day > GetMonthEnd(month)) return b.AddClass("mud-hidden").Build(); - var current = _workingValue ?? GetEffectiveDateTime(Value); + var current = _workingValue ?? ToDateTime(Value); if (current?.Date == day.Date) return b.AddClass("mud-selected") @@ -414,7 +393,7 @@ protected override async Task SubmitAsync() _value = converted; await ValueChanged.InvokeAsync(_value); - await SetTextAsync(GetEffectiveText(_value), false); + await SetTextAsync(ConvertSet(_value), false); await BeginValidateAsync(); FieldChanged(_value); } @@ -426,7 +405,7 @@ protected override async Task SubmitAsync() /// A task that represents the asynchronous operation. public override async Task ClearAsync(bool close = true) { - await SetDateAsync(null, true); + await SetDateAsync(ToDateTime(ValueOnClear), true); if (AutoClose || close) await CloseAsync(false); @@ -439,7 +418,7 @@ public override async Task ClearAsync(bool close = true) protected string GetTitleDateString() { var date = _workingValue - ?? GetEffectiveDateTime(Value) + ?? ToDateTime(Value) ?? TimeProvider.GetLocalNow().Date; return FormatTitleDate(date); @@ -454,7 +433,7 @@ protected string GetTitleDateString() /// or the current local date if neither is set. protected override DateTime GetCalendarStartOfMonth() { - var date = GetEffectiveDateTime(Value) ?? HighlightedDate ?? TimeProvider.GetLocalNow().Date; + var date = ToDateTime(Value) ?? HighlightedDate ?? TimeProvider.GetLocalNow().Date; return date.StartOfMonth(GetCulture()); } @@ -469,7 +448,7 @@ protected override DateTime GetCalendarStartOfMonth() /// The calendar year as an integer, adjusted based on the current value and the specified date. protected override int GetCalendarYear(DateTime yearDate) { - var date = GetEffectiveDateTime(Value) ?? TimeProvider.GetLocalNow().Date; + var date = ToDateTime(Value) ?? TimeProvider.GetLocalNow().Date; var diff = GetCulture().Calendar.GetYear(date) - GetCulture().Calendar.GetYear(yearDate); return GetCulture().Calendar.GetYear(date) - diff; @@ -522,7 +501,7 @@ protected int GetMaxYear() protected Task OnYearClickedAsync(int year) { - var current = GetEffectiveDateTime(Value) ?? TimeProvider.GetLocalNow().Date; + var current = ToDateTime(Value) ?? TimeProvider.GetLocalNow().Date; PickerMonth = new DateTime(year, current.Month, 1); _workingValue = new DateTime( @@ -540,13 +519,13 @@ protected Task OnYearClickedAsync(int year) protected Typo GetYearTypo(int year) { - var current = GetEffectiveDateTime(Value); + var current = ToDateTime(Value); return current?.Year == year ? Typo.h5 : Typo.body1; } protected string GetYearClasses(int year) { - var current = GetEffectiveDateTime(Value); + var current = ToDateTime(Value); return new CssBuilder("mud-picker-year-text") .AddClass("mud-selected", current?.Year == year) @@ -572,7 +551,7 @@ protected IEnumerable GetAllMonths() protected Task OnMonthSelectedAsync(int month) { - var current = _workingValue ?? GetEffectiveDateTime(Value) ?? TimeProvider.GetLocalNow().Date; + var current = _workingValue ?? ToDateTime(Value) ?? TimeProvider.GetLocalNow().Date; PickerMonth = new DateTime(current.Year, month, 1); _workingValue = new DateTime( @@ -604,13 +583,13 @@ protected bool IsMonthDisabled(int month) protected Typo GetMonthTypo(int month) { - var current = GetEffectiveDateTime(Value); + var current = ToDateTime(Value); return current?.Month == month ? Typo.h6 : Typo.body2; } protected string GetMonthClasses(int month) { - var current = GetEffectiveDateTime(Value); + var current = ToDateTime(Value); return new CssBuilder() .AddClass("mud-selected", current?.Month == month) @@ -895,7 +874,7 @@ private void HandleModeChange(PickerMode mode) protected string GetFormattedYearString() { var date = _workingValue - ?? GetEffectiveDateTime(Value) + ?? ToDateTime(Value) ?? TimeProvider.GetLocalNow().Date; return date.Year.ToString(); @@ -914,7 +893,7 @@ public override async Task ScrollToYearAsync(DateTime? date = null) var dateTime = date ?? _workingValue - ?? GetEffectiveDateTime(Value) + ?? ToDateTime(Value) ?? TimeProvider.GetLocalNow().Date; var id = $"{_componentId}{calendar.GetYear(dateTime)}"; diff --git a/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs b/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs index 9db5616f..6d6f037b 100644 --- a/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs +++ b/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs @@ -24,21 +24,6 @@ private IRenderedComponent> RenderPicker( }); } - private static async Task InvokePickerOpenedAsync(IRenderedComponent> comp) - { - var method = typeof(MudDateTimePicker).BaseType!.GetMethod( - "OnPickerOpenedAsync", - System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); - - var task = (Task)method!.Invoke(comp.Instance, [])!; - await task; - } - - private static DateTime GetCurrentMonthStart() - { - return new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); - } - [Test] public void DateTimePicker_DefaultRender_Should_Render_Input() { @@ -120,16 +105,6 @@ public void DateTimePicker_DateOnlyNullable_Should_Render_Empty_When_Null() comp.Find("input").GetAttribute("value").Should().BeNullOrEmpty(); } - [Test] - public async Task DateTimePicker_Default_DateOnly_Should_Open_Current_Month() - { - var comp = RenderPicker(); - - await comp.InvokeAsync(() => InvokePickerOpenedAsync(comp)); - - comp.Instance.PickerMonth.Should().Be(GetCurrentMonthStart()); - } - [Test] public void DateTimePicker_DateTimeOffset_Should_Convert_Correctly() { @@ -223,24 +198,45 @@ public async Task DateTimePicker_Clear_Without_ValueOnClear_Should_Set_Null_For_ } [Test] - public async Task DateTimePicker_Clear_Without_ValueOnClear_DateOnly_Should_Set_Default() + public async Task DateTimePicker_Clear_With_ValueOnClear_Should_Set_Custom_Value_Nullable() + { + DateTime? value = new DateTime(2026, 5, 3, 14, 30, 0); + var customClearValue = new DateTime(2025, 1, 1, 0, 0, 0); + var callback = EventCallback.Factory.Create(this, v => value = v); + + var comp = Context.Render>(parameters => + { + parameters.Add(p => p.Value, value); + parameters.Add(p => p.ValueChanged, callback); + parameters.Add(p => p.ValueOnClear, customClearValue); + }); + + await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); + + value.Should().Be(customClearValue); + } + + [Test] + public async Task DateTimePicker_Clear_With_ValueOnClear_DateOnly_Should_Set_Custom_Value() { DateOnly value = new DateOnly(2026, 5, 3); + var customClearValue = new DateOnly(2025, 1, 1); var callback = EventCallback.Factory.Create(this, v => value = v); var comp = Context.Render>(parameters => { parameters.Add(p => p.Value, value); parameters.Add(p => p.ValueChanged, callback); + parameters.Add(p => p.ValueOnClear, customClearValue); }); await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); - value.Should().Be(default(DateOnly)); + value.Should().Be(customClearValue); } [Test] - public async Task DateTimePicker_Clear_Without_ValueOnClear_DateOnly_Should_Open_Current_Month() + public async Task DateTimePicker_Clear_Without_ValueOnClear_DateOnly_Should_Set_Default() { DateOnly value = new DateOnly(2026, 5, 3); var callback = EventCallback.Factory.Create(this, v => value = v); @@ -252,9 +248,28 @@ public async Task DateTimePicker_Clear_Without_ValueOnClear_DateOnly_Should_Open }); await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); - await comp.InvokeAsync(() => InvokePickerOpenedAsync(comp)); - comp.Instance.PickerMonth.Should().Be(GetCurrentMonthStart()); + value.Should().Be(default(DateOnly)); + } + + [Test] + public async Task DateTimePicker_Clear_With_ValueOnClear_DateTimeOffset_Should_Set_Custom_Value() + { + DateTimeOffset value = new DateTimeOffset(2026, 5, 3, 14, 30, 0, TimeSpan.Zero); + var customClearValue = new DateTimeOffset(2025, 1, 1, 10, 0, 0, TimeSpan.Zero); + var callback = EventCallback.Factory.Create(this, v => value = v); + + var comp = Context.Render>(parameters => + { + parameters.Add(p => p.Value, value); + parameters.Add(p => p.ValueChanged, callback); + parameters.Add(p => p.ValueOnClear, customClearValue); + parameters.Add(p => p.TimeZone, TimeZoneInfo.Utc); + }); + + await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); + + value.Should().Be(customClearValue); } [Test] @@ -274,4 +289,22 @@ public async Task DateTimePicker_Clear_Without_ValueOnClear_DateTimeOffset_Shoul value.Should().Be(default(DateTimeOffset)); } + + [Test] + public async Task DateTimePicker_Clear_With_ValueOnClear_Null_Nullable_Should_Set_Null() + { + DateTime? value = new DateTime(2026, 5, 3, 14, 30, 0); + var callback = EventCallback.Factory.Create(this, v => value = v); + + var comp = Context.Render>(parameters => + { + parameters.Add(p => p.Value, value); + parameters.Add(p => p.ValueChanged, callback); + parameters.Add(p => p.ValueOnClear, null); + }); + + await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); + + value.Should().BeNull(); + } } From 3e026084525f8b1a53673da04eed177ef88da74c Mon Sep 17 00:00:00 2001 From: w3ori <57896209+w3ori@users.noreply.github.com> Date: Mon, 21 Sep 2026 08:22:37 +0200 Subject: [PATCH 09/22] MudDateTimePicker ValueOnClear examples --- .../DateTimePicker/Examples/DateTimePickerExample2.razor | 8 +++++--- .../CodeBeam.MudBlazor.Extensions.csproj | 4 ---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor b/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor index a1d49353..b10a6e6d 100644 --- a/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor +++ b/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor @@ -5,12 +5,14 @@ - + + - + - + + diff --git a/src/CodeBeam.MudBlazor.Extensions/CodeBeam.MudBlazor.Extensions.csproj b/src/CodeBeam.MudBlazor.Extensions/CodeBeam.MudBlazor.Extensions.csproj index 927063af..3e5d7c4d 100644 --- a/src/CodeBeam.MudBlazor.Extensions/CodeBeam.MudBlazor.Extensions.csproj +++ b/src/CodeBeam.MudBlazor.Extensions/CodeBeam.MudBlazor.Extensions.csproj @@ -44,10 +44,6 @@ - - - - From 242b25ce6d2af531eca92b5d552084c3952c6c0d Mon Sep 17 00:00:00 2001 From: w3ori <57896209+w3ori@users.noreply.github.com> Date: Mon, 21 Sep 2026 08:23:46 +0200 Subject: [PATCH 10/22] readd MinifyMudExtensionsJs --- .../CodeBeam.MudBlazor.Extensions.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/CodeBeam.MudBlazor.Extensions/CodeBeam.MudBlazor.Extensions.csproj b/src/CodeBeam.MudBlazor.Extensions/CodeBeam.MudBlazor.Extensions.csproj index 3e5d7c4d..fcdc5753 100644 --- a/src/CodeBeam.MudBlazor.Extensions/CodeBeam.MudBlazor.Extensions.csproj +++ b/src/CodeBeam.MudBlazor.Extensions/CodeBeam.MudBlazor.Extensions.csproj @@ -44,6 +44,10 @@ + + + + From d4e778ec5798735278518a0be7a1ae259dbaf7c4 Mon Sep 17 00:00:00 2001 From: w3ori <57896209+w3ori@users.noreply.github.com> Date: Mon, 21 Sep 2026 08:37:50 +0200 Subject: [PATCH 11/22] fix indent --- .../CodeBeam.MudBlazor.Extensions.csproj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/CodeBeam.MudBlazor.Extensions/CodeBeam.MudBlazor.Extensions.csproj b/src/CodeBeam.MudBlazor.Extensions/CodeBeam.MudBlazor.Extensions.csproj index 11599a43..4ea95298 100644 --- a/src/CodeBeam.MudBlazor.Extensions/CodeBeam.MudBlazor.Extensions.csproj +++ b/src/CodeBeam.MudBlazor.Extensions/CodeBeam.MudBlazor.Extensions.csproj @@ -44,10 +44,10 @@ - - - - + + + + @@ -58,7 +58,7 @@ <_Parameter1>CodeBeam.MudBlazor.Extensions.UnitTests - + True From 276d866838d8fd7c2978bf6aa7bf1e5195303a13 Mon Sep 17 00:00:00 2001 From: w3ori <57896209+w3ori@users.noreply.github.com> Date: Mon, 21 Sep 2026 08:37:57 +0200 Subject: [PATCH 12/22] add test --- .../Components/DateTimePickerTests.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs b/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs index 6d6f037b..e9eeaa44 100644 --- a/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs +++ b/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs @@ -272,6 +272,26 @@ public async Task DateTimePicker_Clear_With_ValueOnClear_DateTimeOffset_Should_S value.Should().Be(customClearValue); } + [Test] + public async Task DateTimePicker_Clear_With_ValueOnClear_DateTimeOffsetNullable_Should_Set_Custom_Value() + { + DateTimeOffset? value = new DateTimeOffset(2026, 5, 3, 14, 30, 0, TimeSpan.Zero); + var customClearValue = new DateTimeOffset(2025, 1, 1, 10, 0, 0, TimeSpan.Zero); + var callback = EventCallback.Factory.Create(this, v => value = v); + + var comp = Context.Render>(parameters => + { + parameters.Add(p => p.Value, value); + parameters.Add(p => p.ValueChanged, callback); + parameters.Add(p => p.ValueOnClear, customClearValue); + parameters.Add(p => p.TimeZone, TimeZoneInfo.Utc); + }); + + await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); + + value.Should().Be(customClearValue); + } + [Test] public async Task DateTimePicker_Clear_Without_ValueOnClear_DateTimeOffset_Should_Set_Default() { From d115b20ff51ca898f93b435ed9c1fc1b532af272 Mon Sep 17 00:00:00 2001 From: w3ori <57896209+w3ori@users.noreply.github.com> Date: Mon, 21 Sep 2026 09:03:27 +0200 Subject: [PATCH 13/22] fix --- .../Examples/DateTimePickerExample2.razor | 26 ++++++++++++------- .../Components/DateTimePickerTests.cs | 19 ++++++++++++++ 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor b/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor index b10a6e6d..ef4db711 100644 --- a/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor +++ b/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor @@ -4,16 +4,25 @@ - - + + - - - + + - - + + @@ -35,5 +44,4 @@ private bool _clearable = false; private bool _editable = false; } - - \ No newline at end of file + diff --git a/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs b/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs index e9eeaa44..0bbbba7c 100644 --- a/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs +++ b/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs @@ -216,6 +216,25 @@ public async Task DateTimePicker_Clear_With_ValueOnClear_Should_Set_Custom_Value value.Should().Be(customClearValue); } + [Test] + public async Task DateTimePicker_Clear_With_ValueOnClear_DateTime_Should_Set_Custom_Value() + { + DateTime value = new DateTime(2026, 5, 3, 14, 30, 0); + var customClearValue = new DateTime(2025, 1, 1, 0, 0, 0); + var callback = EventCallback.Factory.Create(this, v => value = v); + + var comp = Context.Render>(parameters => + { + parameters.Add(p => p.Value, value); + parameters.Add(p => p.ValueChanged, callback); + parameters.Add(p => p.ValueOnClear, customClearValue); + }); + + await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); + + value.Should().Be(customClearValue); + } + [Test] public async Task DateTimePicker_Clear_With_ValueOnClear_DateOnly_Should_Set_Custom_Value() { From 88ddd02c97b5b758c5706b0249393f55a1fd0915 Mon Sep 17 00:00:00 2001 From: w3ori <57896209+w3ori@users.noreply.github.com> Date: Mon, 21 Sep 2026 09:18:56 +0200 Subject: [PATCH 14/22] example 2 --- .../Examples/DateTimePickerExample2.razor | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor b/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor index ef4db711..7b5f2dff 100644 --- a/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor +++ b/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor @@ -9,14 +9,15 @@ - - - + TimeZone="@IstanbulTimeZone" /> @code { + private static readonly TimeZoneInfo IstanbulTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Europe/Istanbul"); private DateTime _dateTime = DateTime.Now; private DateTime? _nullableDateTime; private DateTimeOffset _dateTimeOffset = DateTimeOffset.UtcNow; + private DateTimeOffset _istanbulDateTimeOffset = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, IstanbulTimeZone); private DateTimeOffset? _nullableDateTimeOffset; private DateOnly _dateOnly = DateOnly.FromDateTime(DateTime.Now); private DateOnly? _nullableDateOnly; From 144cf31de72b6a35a1217318e294990d5737c2de Mon Sep 17 00:00:00 2001 From: w3ori <57896209+w3ori@users.noreply.github.com> Date: Mon, 21 Sep 2026 12:47:15 +0200 Subject: [PATCH 15/22] new line --- .../DateTimePicker/Examples/DateTimePickerExample2.razor | 1 + .../Components/DateTimePicker/MudBaseDatePickerX.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor b/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor index 7b5f2dff..230578a2 100644 --- a/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor +++ b/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor @@ -47,4 +47,5 @@ private bool _clearable = false; private bool _editable = false; } + diff --git a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs index f4e08194..96a59270 100644 --- a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs +++ b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs @@ -513,4 +513,4 @@ public virtual Task ScrollToYearAsync(DateTime? date = null) //{ // return JsApiService.UpdateStyleProperty(_mudPickerCalendarContentElementId, "--selected-day", tempId); //} -} \ No newline at end of file +} From d85297316dec9493060709f3784b1b23d5351054 Mon Sep 17 00:00:00 2001 From: w3ori <57896209+w3ori@users.noreply.github.com> Date: Mon, 21 Sep 2026 13:12:48 +0200 Subject: [PATCH 16/22] add tests --- .../Components/DateTimePickerTests.cs | 85 ++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs b/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs index 0bbbba7c..91818619 100644 --- a/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs +++ b/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs @@ -217,11 +217,16 @@ public async Task DateTimePicker_Clear_With_ValueOnClear_Should_Set_Custom_Value } [Test] - public async Task DateTimePicker_Clear_With_ValueOnClear_DateTime_Should_Set_Custom_Value() + public async Task DateTimePicker_Clear_With_ValueOnClear_DateTime_Should_Set_Configured_Value_On_First_Clear() { DateTime value = new DateTime(2026, 5, 3, 14, 30, 0); var customClearValue = new DateTime(2025, 1, 1, 0, 0, 0); - var callback = EventCallback.Factory.Create(this, v => value = v); + var valueChangedCount = 0; + var callback = EventCallback.Factory.Create(this, v => + { + value = v; + valueChangedCount++; + }); var comp = Context.Render>(parameters => { @@ -233,6 +238,43 @@ public async Task DateTimePicker_Clear_With_ValueOnClear_DateTime_Should_Set_Cus await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); value.Should().Be(customClearValue); + valueChangedCount.Should().Be(1); + } + + [Test] + public async Task DateTimePicker_Clear_With_ValueOnClear_CurrentDateTime_Should_Set_Captured_Current_Value() + { + var currentDateTime = DateTime.Now; + DateTime value = currentDateTime.AddDays(-1); + var callback = EventCallback.Factory.Create(this, v => value = v); + + var comp = Context.Render>(parameters => + { + parameters.Add(p => p.Value, value); + parameters.Add(p => p.ValueChanged, callback); + parameters.Add(p => p.ValueOnClear, currentDateTime); + }); + + await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); + + value.Should().Be(currentDateTime); + } + + [Test] + public async Task DateTimePicker_Clear_Without_ValueOnClear_DateTime_Should_Set_Default() + { + DateTime value = new DateTime(2026, 5, 3, 14, 30, 0); + var callback = EventCallback.Factory.Create(this, v => value = v); + + var comp = Context.Render>(parameters => + { + parameters.Add(p => p.Value, value); + parameters.Add(p => p.ValueChanged, callback); + }); + + await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); + + value.Should().Be(default(DateTime)); } [Test] @@ -254,6 +296,25 @@ public async Task DateTimePicker_Clear_With_ValueOnClear_DateOnly_Should_Set_Cus value.Should().Be(customClearValue); } + [Test] + public async Task DateTimePicker_Clear_With_ValueOnClear_CurrentDate_Should_Set_Captured_Current_Date() + { + var currentDate = DateOnly.FromDateTime(DateTime.Now); + DateOnly value = currentDate.AddDays(-1); + var callback = EventCallback.Factory.Create(this, v => value = v); + + var comp = Context.Render>(parameters => + { + parameters.Add(p => p.Value, value); + parameters.Add(p => p.ValueChanged, callback); + parameters.Add(p => p.ValueOnClear, currentDate); + }); + + await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); + + value.Should().Be(currentDate); + } + [Test] public async Task DateTimePicker_Clear_Without_ValueOnClear_DateOnly_Should_Set_Default() { @@ -291,6 +352,26 @@ public async Task DateTimePicker_Clear_With_ValueOnClear_DateTimeOffset_Should_S value.Should().Be(customClearValue); } + [Test] + public async Task DateTimePicker_Clear_With_ValueOnClear_CurrentDateTimeOffset_Should_Set_Captured_Current_Value() + { + var currentDateTimeOffset = DateTimeOffset.UtcNow; + DateTimeOffset value = currentDateTimeOffset.AddDays(-1); + var callback = EventCallback.Factory.Create(this, v => value = v); + + var comp = Context.Render>(parameters => + { + parameters.Add(p => p.Value, value); + parameters.Add(p => p.ValueChanged, callback); + parameters.Add(p => p.ValueOnClear, currentDateTimeOffset); + parameters.Add(p => p.TimeZone, TimeZoneInfo.Utc); + }); + + await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); + + value.Should().Be(currentDateTimeOffset); + } + [Test] public async Task DateTimePicker_Clear_With_ValueOnClear_DateTimeOffsetNullable_Should_Set_Custom_Value() { From 40dd028ac6ace7ec3a8cb898b604f48e8a3e5380 Mon Sep 17 00:00:00 2001 From: w3ori <57896209+w3ori@users.noreply.github.com> Date: Tue, 22 Sep 2026 07:45:13 +0200 Subject: [PATCH 17/22] undo --- .../wwwroot/CodeBeam.MudBlazor.Extensions.xml | 15 +- .../Examples/DateTimePickerExample2.razor | 11 +- .../DateTimePicker/MudBaseDatePickerX.cs | 20 +- .../DateTimePicker/MudDateTimePicker.razor.cs | 10 +- .../Components/DateTimePickerTests.cs | 329 ------------------ 5 files changed, 22 insertions(+), 363 deletions(-) diff --git a/docs/CodeBeam.MudBlazor.Extensions.Docs.Wasm/wwwroot/CodeBeam.MudBlazor.Extensions.xml b/docs/CodeBeam.MudBlazor.Extensions.Docs.Wasm/wwwroot/CodeBeam.MudBlazor.Extensions.xml index 739e3c66..762f518c 100644 --- a/docs/CodeBeam.MudBlazor.Extensions.Docs.Wasm/wwwroot/CodeBeam.MudBlazor.Extensions.xml +++ b/docs/CodeBeam.MudBlazor.Extensions.Docs.Wasm/wwwroot/CodeBeam.MudBlazor.Extensions.xml @@ -1452,15 +1452,6 @@ Defaults to false. - - - The value to set when the clear button is clicked. - - - Defaults to default.
- For nullable types, this will default to null. For non-nullable types, this will default to the minimum value. -
-
The function used to disable one or more dates. @@ -1608,9 +1599,9 @@ Shows a 12-hour selection clock. - Defaults to false.
- When true, hours 1-12 are displayed with an AM or PM marker.
- When false, hours 0-23 are displayed.
+ Defaults to false. + When true, hours 1-12 are displayed with an AM or PM marker. + When false, hours 0-23 are displayed.
diff --git a/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor b/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor index 230578a2..25dc772c 100644 --- a/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor +++ b/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor @@ -4,23 +4,20 @@ - - - - diff --git a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs index 96a59270..670048c1 100644 --- a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs +++ b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs @@ -185,16 +185,6 @@ public DateTime? PickerMonth /// [Parameter] public bool AutoClose { get; set; } - /// - /// The value to set when the clear button is clicked. - /// - /// - /// Defaults to default.
- /// For nullable types, this will default to null. For non-nullable types, this will default to the minimum value. - ///
- [Parameter] - public T? ValueOnClear { get; set; } = default; - /// /// The function used to disable one or more dates. /// @@ -384,6 +374,16 @@ protected override async Task OnPickerOpenedAsync() 1, calendar); } + else + { + var culture = GetCulture(); + var calendar = culture.Calendar; + PickerMonth = new DateTime( + calendar.GetYear(DateTime.Today), + calendar.GetMonth(DateTime.Today), + 1, + calendar); + } CurrentView = OpenTo; } diff --git a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs index f681e774..ddbef020 100644 --- a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs +++ b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs @@ -110,9 +110,9 @@ public T? Value /// Shows a 12-hour selection clock. ///
/// - /// Defaults to false.
- /// When true, hours 1-12 are displayed with an AM or PM marker.
- /// When false, hours 0-23 are displayed.
+ /// Defaults to false. + /// When true, hours 1-12 are displayed with an AM or PM marker. + /// When false, hours 0-23 are displayed. ///
[Parameter] public bool AmPm @@ -405,9 +405,9 @@ protected override async Task SubmitAsync() /// A task that represents the asynchronous operation. public override async Task ClearAsync(bool close = true) { - await SetDateAsync(ToDateTime(ValueOnClear), true); + await SetDateAsync(null, true); - if (AutoClose || close) + if (AutoClose) await CloseAsync(false); } diff --git a/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs b/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs index 91818619..0fec0d9d 100644 --- a/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs +++ b/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs @@ -43,32 +43,6 @@ public void DateTimePicker_Should_Format_Correctly() text.Should().Be("03.05.2026 14:30"); } - [Test] - public void DateTimePicker_DateTime_Should_Render_Time() - { - var comp = RenderPicker( - value: new DateTime(2026, 5, 3, 14, 30, 0), - format: "dd.MM.yyyy HH:mm"); - - comp.Instance.ConvertSetInternal(comp.Instance.Value).Should().Be("03.05.2026 14:30"); - } - - [Test] - public void DateTimePicker_DateTimeNullable_Should_Render_Empty_When_Null() - { - var comp = RenderPicker(); - - comp.Find("input").GetAttribute("value").Should().BeNullOrEmpty(); - } - - [Test] - public void DateTimePicker_Default_DateTime_Should_Render_Empty() - { - var comp = RenderPicker(); - - comp.Find("input").GetAttribute("value").Should().BeNullOrEmpty(); - } - [Test] public void DateTimePicker_DateOnly_Should_Not_Render_Time() { @@ -79,49 +53,8 @@ public void DateTimePicker_DateOnly_Should_Not_Render_Time() comp.Markup.Should().NotContain("mud-picker-time"); } - [Test] - public void DateTimePicker_DateOnly_Should_Render_Date_Only() - { - var comp = RenderPicker( - value: new DateOnly(2026, 5, 3), - format: "dd.MM.yyyy"); - - comp.Instance.ConvertSetInternal(comp.Instance.Value).Should().Be("03.05.2026"); - } - - [Test] - public void DateTimePicker_Default_DateOnly_Should_Render_Empty() - { - var comp = RenderPicker(); - - comp.Find("input").GetAttribute("value").Should().BeNullOrEmpty(); - } - - [Test] - public void DateTimePicker_DateOnlyNullable_Should_Render_Empty_When_Null() - { - var comp = RenderPicker(); - - comp.Find("input").GetAttribute("value").Should().BeNullOrEmpty(); - } - [Test] public void DateTimePicker_DateTimeOffset_Should_Convert_Correctly() - { - DateTimeOffset value = new DateTimeOffset(2026, 5, 3, 10, 0, 0, TimeSpan.Zero); - - var comp = Context.Render>(p => p - .Add(x => x.Value, value) - .Add(x => x.TimeZone, TimeZoneInfo.Utc) - ); - - var dt = comp.Instance.ToDateTime(value); - - dt.Should().Be(new DateTime(2026, 5, 3, 10, 0, 0)); - } - - [Test] - public void DateTimePicker_DateTimeOffsetNullable_Should_Convert_Correctly() { DateTimeOffset? value = new DateTimeOffset(2026, 5, 3, 10, 0, 0, TimeSpan.Zero); @@ -135,22 +68,6 @@ public void DateTimePicker_DateTimeOffsetNullable_Should_Convert_Correctly() dt.Should().Be(new DateTime(2026, 5, 3, 10, 0, 0)); } - [Test] - public void DateTimePicker_DateTimeOffsetNullable_Should_Render_Empty_When_Null() - { - var comp = RenderPicker(); - - comp.Find("input").GetAttribute("value").Should().BeNullOrEmpty(); - } - - [Test] - public void DateTimePicker_Default_DateTimeOffset_Should_Render_Empty() - { - var comp = RenderPicker(); - - comp.Find("input").GetAttribute("value").Should().BeNullOrEmpty(); - } - [Test] public async Task DateTimePicker_Input_Change_Should_Update_Value() { @@ -181,250 +98,4 @@ public void DateTimePicker_Null_Value_Should_Render_Empty() var comp = RenderPicker(); comp.Find("input").GetAttribute("value").Should().BeNullOrEmpty(); } - - [Test] - public async Task DateTimePicker_Clear_Without_ValueOnClear_Should_Set_Null_For_Nullable() - { - DateTime? value = new DateTime(2026, 5, 3, 14, 30, 0); - var callback = EventCallback.Factory.Create(this, v => value = v); - - var comp = RenderPicker( - value: value, - valueChanged: callback); - - await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); - - value.Should().BeNull(); - } - - [Test] - public async Task DateTimePicker_Clear_With_ValueOnClear_Should_Set_Custom_Value_Nullable() - { - DateTime? value = new DateTime(2026, 5, 3, 14, 30, 0); - var customClearValue = new DateTime(2025, 1, 1, 0, 0, 0); - var callback = EventCallback.Factory.Create(this, v => value = v); - - var comp = Context.Render>(parameters => - { - parameters.Add(p => p.Value, value); - parameters.Add(p => p.ValueChanged, callback); - parameters.Add(p => p.ValueOnClear, customClearValue); - }); - - await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); - - value.Should().Be(customClearValue); - } - - [Test] - public async Task DateTimePicker_Clear_With_ValueOnClear_DateTime_Should_Set_Configured_Value_On_First_Clear() - { - DateTime value = new DateTime(2026, 5, 3, 14, 30, 0); - var customClearValue = new DateTime(2025, 1, 1, 0, 0, 0); - var valueChangedCount = 0; - var callback = EventCallback.Factory.Create(this, v => - { - value = v; - valueChangedCount++; - }); - - var comp = Context.Render>(parameters => - { - parameters.Add(p => p.Value, value); - parameters.Add(p => p.ValueChanged, callback); - parameters.Add(p => p.ValueOnClear, customClearValue); - }); - - await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); - - value.Should().Be(customClearValue); - valueChangedCount.Should().Be(1); - } - - [Test] - public async Task DateTimePicker_Clear_With_ValueOnClear_CurrentDateTime_Should_Set_Captured_Current_Value() - { - var currentDateTime = DateTime.Now; - DateTime value = currentDateTime.AddDays(-1); - var callback = EventCallback.Factory.Create(this, v => value = v); - - var comp = Context.Render>(parameters => - { - parameters.Add(p => p.Value, value); - parameters.Add(p => p.ValueChanged, callback); - parameters.Add(p => p.ValueOnClear, currentDateTime); - }); - - await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); - - value.Should().Be(currentDateTime); - } - - [Test] - public async Task DateTimePicker_Clear_Without_ValueOnClear_DateTime_Should_Set_Default() - { - DateTime value = new DateTime(2026, 5, 3, 14, 30, 0); - var callback = EventCallback.Factory.Create(this, v => value = v); - - var comp = Context.Render>(parameters => - { - parameters.Add(p => p.Value, value); - parameters.Add(p => p.ValueChanged, callback); - }); - - await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); - - value.Should().Be(default(DateTime)); - } - - [Test] - public async Task DateTimePicker_Clear_With_ValueOnClear_DateOnly_Should_Set_Custom_Value() - { - DateOnly value = new DateOnly(2026, 5, 3); - var customClearValue = new DateOnly(2025, 1, 1); - var callback = EventCallback.Factory.Create(this, v => value = v); - - var comp = Context.Render>(parameters => - { - parameters.Add(p => p.Value, value); - parameters.Add(p => p.ValueChanged, callback); - parameters.Add(p => p.ValueOnClear, customClearValue); - }); - - await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); - - value.Should().Be(customClearValue); - } - - [Test] - public async Task DateTimePicker_Clear_With_ValueOnClear_CurrentDate_Should_Set_Captured_Current_Date() - { - var currentDate = DateOnly.FromDateTime(DateTime.Now); - DateOnly value = currentDate.AddDays(-1); - var callback = EventCallback.Factory.Create(this, v => value = v); - - var comp = Context.Render>(parameters => - { - parameters.Add(p => p.Value, value); - parameters.Add(p => p.ValueChanged, callback); - parameters.Add(p => p.ValueOnClear, currentDate); - }); - - await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); - - value.Should().Be(currentDate); - } - - [Test] - public async Task DateTimePicker_Clear_Without_ValueOnClear_DateOnly_Should_Set_Default() - { - DateOnly value = new DateOnly(2026, 5, 3); - var callback = EventCallback.Factory.Create(this, v => value = v); - - var comp = Context.Render>(parameters => - { - parameters.Add(p => p.Value, value); - parameters.Add(p => p.ValueChanged, callback); - }); - - await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); - - value.Should().Be(default(DateOnly)); - } - - [Test] - public async Task DateTimePicker_Clear_With_ValueOnClear_DateTimeOffset_Should_Set_Custom_Value() - { - DateTimeOffset value = new DateTimeOffset(2026, 5, 3, 14, 30, 0, TimeSpan.Zero); - var customClearValue = new DateTimeOffset(2025, 1, 1, 10, 0, 0, TimeSpan.Zero); - var callback = EventCallback.Factory.Create(this, v => value = v); - - var comp = Context.Render>(parameters => - { - parameters.Add(p => p.Value, value); - parameters.Add(p => p.ValueChanged, callback); - parameters.Add(p => p.ValueOnClear, customClearValue); - parameters.Add(p => p.TimeZone, TimeZoneInfo.Utc); - }); - - await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); - - value.Should().Be(customClearValue); - } - - [Test] - public async Task DateTimePicker_Clear_With_ValueOnClear_CurrentDateTimeOffset_Should_Set_Captured_Current_Value() - { - var currentDateTimeOffset = DateTimeOffset.UtcNow; - DateTimeOffset value = currentDateTimeOffset.AddDays(-1); - var callback = EventCallback.Factory.Create(this, v => value = v); - - var comp = Context.Render>(parameters => - { - parameters.Add(p => p.Value, value); - parameters.Add(p => p.ValueChanged, callback); - parameters.Add(p => p.ValueOnClear, currentDateTimeOffset); - parameters.Add(p => p.TimeZone, TimeZoneInfo.Utc); - }); - - await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); - - value.Should().Be(currentDateTimeOffset); - } - - [Test] - public async Task DateTimePicker_Clear_With_ValueOnClear_DateTimeOffsetNullable_Should_Set_Custom_Value() - { - DateTimeOffset? value = new DateTimeOffset(2026, 5, 3, 14, 30, 0, TimeSpan.Zero); - var customClearValue = new DateTimeOffset(2025, 1, 1, 10, 0, 0, TimeSpan.Zero); - var callback = EventCallback.Factory.Create(this, v => value = v); - - var comp = Context.Render>(parameters => - { - parameters.Add(p => p.Value, value); - parameters.Add(p => p.ValueChanged, callback); - parameters.Add(p => p.ValueOnClear, customClearValue); - parameters.Add(p => p.TimeZone, TimeZoneInfo.Utc); - }); - - await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); - - value.Should().Be(customClearValue); - } - - [Test] - public async Task DateTimePicker_Clear_Without_ValueOnClear_DateTimeOffset_Should_Set_Default() - { - DateTimeOffset value = new DateTimeOffset(2026, 5, 3, 14, 30, 0, TimeSpan.Zero); - var callback = EventCallback.Factory.Create(this, v => value = v); - - var comp = Context.Render>(parameters => - { - parameters.Add(p => p.Value, value); - parameters.Add(p => p.ValueChanged, callback); - parameters.Add(p => p.TimeZone, TimeZoneInfo.Utc); - }); - - await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); - - value.Should().Be(default(DateTimeOffset)); - } - - [Test] - public async Task DateTimePicker_Clear_With_ValueOnClear_Null_Nullable_Should_Set_Null() - { - DateTime? value = new DateTime(2026, 5, 3, 14, 30, 0); - var callback = EventCallback.Factory.Create(this, v => value = v); - - var comp = Context.Render>(parameters => - { - parameters.Add(p => p.Value, value); - parameters.Add(p => p.ValueChanged, callback); - parameters.Add(p => p.ValueOnClear, null); - }); - - await comp.InvokeAsync(async () => await comp.Instance.ClearAsync(false)); - - value.Should().BeNull(); - } } From ccea6a3395d260c0f989a2cc4d5c409c9852729a Mon Sep 17 00:00:00 2001 From: w3ori <57896209+w3ori@users.noreply.github.com> Date: Tue, 22 Sep 2026 09:07:45 +0200 Subject: [PATCH 18/22] open at current date --- .../DateTimePicker/MudBaseDatePickerX.cs | 12 +++++----- .../DateTimePicker/MudDateTimePicker.razor.cs | 23 ++++++++++++++----- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs index 670048c1..86adf7ea 100644 --- a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs +++ b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs @@ -364,23 +364,23 @@ protected override async Task OnPickerOpenedAsync() var dateTime = ToDateTime(_value); - if (dateTime.HasValue) + if (dateTime.HasValue && dateTime.Value == default) { var culture = GetCulture(); var calendar = culture.Calendar; PickerMonth = new DateTime( - calendar.GetYear(dateTime.Value), - calendar.GetMonth(dateTime.Value), + calendar.GetYear(DateTime.Today), + calendar.GetMonth(DateTime.Today), 1, calendar); } - else + else if (dateTime.HasValue) { var culture = GetCulture(); var calendar = culture.Calendar; PickerMonth = new DateTime( - calendar.GetYear(DateTime.Today), - calendar.GetMonth(DateTime.Today), + calendar.GetYear(dateTime.Value), + calendar.GetMonth(dateTime.Value), 1, calendar); } diff --git a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs index ddbef020..57b876b2 100644 --- a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs +++ b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs @@ -417,13 +417,26 @@ public override async Task ClearAsync(bool close = true) /// The formatted date string for the title of the picker. protected string GetTitleDateString() { - var date = _workingValue - ?? ToDateTime(Value) - ?? TimeProvider.GetLocalNow().Date; + var date = GetPickerHeaderDate(_workingValue ?? ToDateTime(Value)); return FormatTitleDate(date); } + /// + /// Gets the date used in the picker header. + /// + /// + /// A non-nullable date picker uses as its cleared value. The header uses the + /// current local date as the picker reference date for that sentinel instead of presenting it as a selected value. + /// + private DateTime GetPickerHeaderDate(DateTime? date) + { + if (date is not null && date.Value != DateTime.MinValue) + return date.Value; + + return TimeProvider.GetLocalNow().Date; + } + /// /// Calculates the first day of the month for the current calendar context. /// @@ -873,9 +886,7 @@ private void HandleModeChange(PickerMode mode) protected string GetFormattedYearString() { - var date = _workingValue - ?? ToDateTime(Value) - ?? TimeProvider.GetLocalNow().Date; + var date = GetPickerHeaderDate(_workingValue ?? ToDateTime(Value)); return date.Year.ToString(); } From 56686da21a4bcd2efa4935f3610566800bcef2ec Mon Sep 17 00:00:00 2001 From: w3ori <57896209+w3ori@users.noreply.github.com> Date: Tue, 22 Sep 2026 09:12:16 +0200 Subject: [PATCH 19/22] add missing xml comments --- .../DateTimePicker/MudBaseDatePickerX.cs | 75 ++++++++++ .../DateTimePicker/MudDateTimePicker.razor.cs | 138 ++++++++++++++++++ 2 files changed, 213 insertions(+) diff --git a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs index 86adf7ea..44d94814 100644 --- a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs +++ b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs @@ -13,6 +13,9 @@ public abstract partial class MudBaseDatePickerX : MudPicker { internal readonly string _mudPickerCalendarContentElementId; private readonly ParameterState _formatState; + /// + /// The unique identifier used to associate picker elements. + /// protected readonly string _componentId = Identifier.Create(); internal DateTime? _picker_month; @@ -306,6 +309,9 @@ public DateTime? PickerMonth throw new NotSupportedException($"Type {typeof(T)} not supported"); } + /// + /// Validates that is a supported date type. + /// protected void ValidateType() { var t = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T); @@ -341,6 +347,11 @@ protected override async Task OnAfterRenderAsync(bool firstRender) ScrollToYearAsync().CatchAndLog(); } + /// + /// Handles a change to the configured date format. + /// + /// The newly configured date format. + /// A task that represents the asynchronous operation. protected virtual Task DateFormatChangedAsync(string? newFormat) => Task.CompletedTask; private Task DateFormatChangedAsync(ParameterChangedEventArgs args) => DateFormatChangedAsync(args.Value); @@ -358,6 +369,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) || CurrentView == OpenTo.Month || CurrentView == OpenTo.Year; + /// protected override async Task OnPickerOpenedAsync() { await base.OnPickerOpenedAsync(); @@ -388,6 +400,11 @@ protected override async Task OnPickerOpenedAsync() CurrentView = OpenTo; } + /// + /// Gets the first day of the month at the specified offset from the displayed month. + /// + /// The zero-based offset from the displayed month. + /// The first day of the requested month. protected DateTime GetMonthStart(int month) { var culture = GetCulture(); @@ -397,6 +414,12 @@ protected DateTime GetMonthStart(int month) return calendar.AddMonths(new DateTime(baseDate.Year, baseDate.Month, 1), month); } + /// + /// Gets the seven days displayed for a calendar week. + /// + /// The zero-based offset from the displayed month. + /// The zero-based week index. + /// The dates in the requested week. protected IEnumerable GetWeek(int month, int index) { if (index is < 0 or > 5) @@ -413,6 +436,11 @@ protected IEnumerable GetWeek(int month, int index) yield return weekFirst.AddDays(i); } + /// + /// Determines whether a date is unavailable for selection. + /// + /// The date to evaluate. + /// when the date is disabled; otherwise, . protected virtual bool IsDayDisabled(DateTime date) { return date < MinDate || @@ -420,14 +448,35 @@ protected virtual bool IsDayDisabled(DateTime date) IsDateDisabledFunc(date); } + /// + /// Gets the CSS classes for a calendar day. + /// + /// The zero-based offset from the displayed month. + /// The day to format. + /// The CSS classes for the day. protected abstract string GetDayClasses(int month, DateTime day); + + /// + /// Handles selection of a calendar day. + /// + /// The selected date and time. + /// A task that represents the asynchronous operation. protected abstract Task OnDayClickedAsync(DateTime dateTime); + /// + /// Formats a date for the picker title. + /// + /// The date to format. + /// The formatted title, or an empty string when is . protected string FormatTitleDate(DateTime? date) { return date?.ToString(TitleDateFormat, GetCulture()) ?? ""; } + /// + /// Gets abbreviated day names ordered according to the configured first day of the week. + /// + /// The ordered abbreviated day names. protected IEnumerable GetAbbreviatedDayNames() { var culture = GetCulture(); @@ -438,6 +487,7 @@ protected IEnumerable GetAbbreviatedDayNames() return Enumerable.Range(0, 7).Select(i => names[(i + firstDay) % 7]); } + /// protected override IConverter GetDefaultConverter() { return new DefaultConverter @@ -447,6 +497,7 @@ protected IEnumerable GetAbbreviatedDayNames() }; } + /// protected override string? ConvertSet(T? value) { var dt = ToDateTime(value); @@ -457,6 +508,11 @@ protected IEnumerable GetAbbreviatedDayNames() return dt.Value.ToString(GetFormat(), GetCulture()); } + /// + /// Converts a picker value to its display text. + /// + /// The value to convert. + /// The formatted text, or when the value has no date representation. protected internal string? ConvertSetInternal(T? value) { return ConvertSet(value); @@ -482,14 +538,33 @@ protected override string GetFormat() return $"{CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern} HH:mm"; } + /// + /// Gets the initial month shown by the calendar. + /// + /// The first day of the initial month. protected abstract DateTime GetCalendarStartOfMonth(); + + /// + /// Gets the culture-specific calendar year for a date. + /// + /// The date whose calendar year is required. + /// The calendar year. protected abstract int GetCalendarYear(DateTime yearDate); + /// + /// Gets the first day of the calendar week. + /// + /// The configured first day of the week, or the culture default. protected DayOfWeek GetFirstDayOfWeek() { return FirstDayOfWeek ?? GetCulture().DateTimeFormat.FirstDayOfWeek; } + /// + /// Gets the last day of the month at the specified offset from the displayed month. + /// + /// The zero-based offset from the displayed month. + /// The last day of the requested month. protected DateTime GetMonthEnd(int month) { var culture = GetCulture(); diff --git a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs index 57b876b2..5973babc 100644 --- a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs +++ b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs @@ -18,6 +18,9 @@ public partial class MudDateTimePicker : MudBaseDatePickerX [DynamicDependency(nameof(OnStickClick))] [DynamicDependency(nameof(SelectTimeFromStick))] + /// + /// Initializes a new instance of the class. + /// public MudDateTimePicker() { _dotNetReferenceLazy = new Lazy>>(CreateDotNetObjectReference); @@ -34,12 +37,25 @@ public MudDateTimePicker() private record SetTime { + /// + /// Gets or sets the selected hour. + /// public int Hour { get; set; } + + /// + /// Gets or sets the selected minute. + /// public int Minute { get; set; } } + /// + /// Gets or sets whether the clock pointer is currently being dragged. + /// public bool PointerMoving { get; set; } + /// + /// Gets the element reference for the clock surface. + /// protected ElementReference ClockElementReference { get; private set; } private bool _amPm = false; @@ -85,6 +101,9 @@ private void SyncTimeFromValue() _timeSet.Minute = _workingValue.Value.Minute; } + /// + /// The active picker mode. + /// protected PickerMode _mode = PickerMode.Date; /// @@ -176,6 +195,7 @@ private int RoundToStepInterval(int value) [Parameter] public string PmText { get; set; } = "PM"; + /// protected override async Task WriteTextAsync(string? text) { if (string.IsNullOrWhiteSpace(text)) @@ -230,6 +250,12 @@ private async Task OnPmClickedAsync() private DateTimeOffset _lastSetTime = DateTimeOffset.MinValue; private const int DebounceTimeoutMs = 100; + /// + /// Sets the selected date and optionally updates the input text and bound value. + /// + /// The date to set, or to clear the selection. + /// Whether to update the displayed text and reset conversion errors. + /// A task that represents the asynchronous operation. protected internal async Task SetDateAsync(DateTime? date, bool updateValue) { var current = ToDateTime(_value); @@ -326,6 +352,7 @@ private void SetTimePart(int hour, int minute) ); } + /// protected override string GetDayClasses(int month, DateTime day) { var b = new CssBuilder("mud-day"); @@ -467,18 +494,31 @@ protected override int GetCalendarYear(DateTime yearDate) return GetCulture().Calendar.GetYear(date) - diff; } + /// + /// Gets the localized name of a displayed month. + /// + /// The zero-based offset from the displayed month. + /// The localized month name and year. protected string GetMonthName(int month) { var date = GetMonthStart(month); return date.ToString("MMMM yyyy", GetCulture()); } + /// + /// Moves the calendar to the previous month. + /// + /// A completed task. protected Task OnPreviousMonthClick() { PickerMonth = GetMonthStart(0).AddMonths(-1); return Task.CompletedTask; } + /// + /// Moves the calendar to the next month. + /// + /// A completed task. protected Task OnNextMonthClick() { PickerMonth = GetMonthStart(0).AddMonths(1); @@ -502,16 +542,29 @@ private void OnYearClick() } } + /// + /// Gets the earliest selectable year. + /// + /// The year from , or 1900 when no minimum is set. protected int GetMinYear() { return MinDate?.Year ?? 1900; } + /// + /// Gets the latest selectable year. + /// + /// The year from , or 2100 when no maximum is set. protected int GetMaxYear() { return MaxDate?.Year ?? 2100; } + /// + /// Handles selection of a year in the calendar. + /// + /// The selected year. + /// A task that represents the asynchronous operation. protected Task OnYearClickedAsync(int year) { var current = ToDateTime(Value) ?? TimeProvider.GetLocalNow().Date; @@ -530,12 +583,22 @@ protected Task OnYearClickedAsync(int year) return Task.CompletedTask; } + /// + /// Gets the typography style for a year. + /// + /// The year to evaluate. + /// The typography style for the year. protected Typo GetYearTypo(int year) { var current = ToDateTime(Value); return current?.Year == year ? Typo.h5 : Typo.body1; } + /// + /// Gets the CSS classes for a year. + /// + /// The year to evaluate. + /// The CSS classes for the year. protected string GetYearClasses(int year) { var current = ToDateTime(Value); @@ -545,23 +608,40 @@ protected string GetYearClasses(int year) .Build(); } + /// + /// Moves the calendar to the previous year. + /// + /// A completed task. protected Task OnPreviousYearClick() { PickerMonth = (PickerMonth ?? DateTime.Today).AddYears(-1); return Task.CompletedTask; } + /// + /// Moves the calendar to the next year. + /// + /// A completed task. protected Task OnNextYearClick() { PickerMonth = (PickerMonth ?? DateTime.Today).AddYears(1); return Task.CompletedTask; } + /// + /// Gets all month numbers used by the month-selection view. + /// + /// The month numbers from 1 through 12. protected IEnumerable GetAllMonths() { return Enumerable.Range(1, 12); } + /// + /// Handles selection of a month in the calendar. + /// + /// The selected month number. + /// A task that represents the asynchronous operation. protected Task OnMonthSelectedAsync(int month) { var current = _workingValue ?? ToDateTime(Value) ?? TimeProvider.GetLocalNow().Date; @@ -580,6 +660,11 @@ protected Task OnMonthSelectedAsync(int month) return Task.CompletedTask; } + /// + /// Determines whether every day in a month is outside the configured date range. + /// + /// The month number to evaluate. + /// when the month is disabled; otherwise, . protected bool IsMonthDisabled(int month) { if (!MinDate.HasValue && !MaxDate.HasValue) @@ -594,12 +679,22 @@ protected bool IsMonthDisabled(int month) || (MaxDate.HasValue && start > MaxDate.Value); } + /// + /// Gets the typography style for a month. + /// + /// The month number to evaluate. + /// The typography style for the month. protected Typo GetMonthTypo(int month) { var current = ToDateTime(Value); return current?.Month == month ? Typo.h6 : Typo.body2; } + /// + /// Gets the CSS classes for a month. + /// + /// The month number to evaluate. + /// The CSS classes for the month. protected string GetMonthClasses(int month) { var current = ToDateTime(Value); @@ -609,11 +704,22 @@ protected string GetMonthClasses(int month) .Build(); } + /// + /// Gets the localized abbreviated name of a month. + /// + /// The month number. + /// The abbreviated month name. protected string GetAbbreviatedMonthName(int month) { return GetCulture().DateTimeFormat.AbbreviatedMonthNames[month - 1]; } + /// + /// Gets the culture-specific week number for a displayed calendar week. + /// + /// The zero-based offset from the displayed month. + /// The zero-based week index. + /// The week number. protected int GetWeekNumber(int month, int week) { var firstDay = GetWeek(month, week).First(); @@ -624,16 +730,28 @@ protected int GetWeekNumber(int month, int week) GetFirstDayOfWeek()); } + /// + /// Gets the day-of-month text for a calendar date. + /// + /// The date to format. + /// The localized day-of-month text. protected string GetCalendarDayOfMonth(DateTime date) { return date.Day.ToString(GetCulture()); } + /// + /// Switches the picker to month selection. + /// protected void OnFormattedDateClick() { CurrentView = OpenTo.Month; } + /// + /// Switches the picker to month selection. + /// + /// The clicked month number. protected void OnMonthClicked(int month) { CurrentView = OpenTo.Month; @@ -764,6 +882,12 @@ private static string GetTransform(double angle, double radius, double offsetX, } [JSInvokable] + /// + /// Updates the working time from a clock-pointer movement. + /// + /// The hour or minute indicated by the clock pointer. + /// Whether the pointer is currently being dragged. + /// A task that represents the asynchronous operation. public async Task SelectTimeFromStick(int value, bool pointerMoving) { PointerMoving = pointerMoving; @@ -779,6 +903,11 @@ public async Task SelectTimeFromStick(int value, bool pointerMoving) } [JSInvokable] + /// + /// Handles a click on the clock pointer and advances or submits the time selection. + /// + /// The selected hour or minute. + /// A task that represents the asynchronous operation. public async Task OnStickClick(int value) { // The pointer is up and not moving so animations can be enabled again. @@ -805,6 +934,10 @@ public async Task OnStickClick(int value) StateHasChanged(); } + /// + /// Submits the working value and closes the picker when its configuration permits it. + /// + /// A task that represents the asynchronous operation. protected async Task SubmitAndCloseAsync() { if (PickerActions == null || AutoClose) @@ -884,6 +1017,10 @@ private void HandleModeChange(PickerMode mode) } } + /// + /// Gets the year displayed in the picker toolbar. + /// + /// The formatted year. protected string GetFormattedYearString() { var date = GetPickerHeaderDate(_workingValue ?? ToDateTime(Value)); @@ -914,6 +1051,7 @@ public override async Task ScrollToYearAsync(DateTime? date = null) StateHasChanged(); } + /// protected override async Task OnOpenedAsync() { _mode = PickerMode.Date; From ba2b5f248587db1c16a70762c2e1e8ccaa32728c Mon Sep 17 00:00:00 2001 From: w3ori <57896209+w3ori@users.noreply.github.com> Date: Tue, 22 Sep 2026 09:15:23 +0200 Subject: [PATCH 20/22] fix comments --- .../DateTimePicker/MudDateTimePicker.razor.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs index 5973babc..192394de 100644 --- a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs +++ b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs @@ -16,11 +16,12 @@ public partial class MudDateTimePicker : MudBaseDatePickerX { [Inject] private IJSRuntime JsRuntime { get; set; } = null!; - [DynamicDependency(nameof(OnStickClick))] - [DynamicDependency(nameof(SelectTimeFromStick))] + /// /// Initializes a new instance of the class. /// + [DynamicDependency(nameof(OnStickClick))] + [DynamicDependency(nameof(SelectTimeFromStick))] public MudDateTimePicker() { _dotNetReferenceLazy = new Lazy>>(CreateDotNetObjectReference); @@ -426,7 +427,7 @@ protected override async Task SubmitAsync() } /// - /// Clears the selected date and time, resetting the component to its initial state. If is true, the picker will also close after clearing the value. + /// Clears the selected date and time, resetting the component to its initial state. If AutoClose is true, the picker will also close after clearing the value. /// /// Indicates whether the picker should close after clearing the value. /// A task that represents the asynchronous operation. @@ -545,7 +546,7 @@ private void OnYearClick() /// /// Gets the earliest selectable year. /// - /// The year from , or 1900 when no minimum is set. + /// The year from MinDate, or 1900 when no minimum is set. protected int GetMinYear() { return MinDate?.Year ?? 1900; @@ -554,7 +555,7 @@ protected int GetMinYear() /// /// Gets the latest selectable year. /// - /// The year from , or 2100 when no maximum is set. + /// The year from MaxDate, or 2100 when no maximum is set. protected int GetMaxYear() { return MaxDate?.Year ?? 2100; @@ -881,13 +882,13 @@ private static string GetTransform(double angle, double radius, double offsetX, return $"transform: translate({x}px, {y}px);"; } - [JSInvokable] /// /// Updates the working time from a clock-pointer movement. /// /// The hour or minute indicated by the clock pointer. /// Whether the pointer is currently being dragged. /// A task that represents the asynchronous operation. + [JSInvokable] public async Task SelectTimeFromStick(int value, bool pointerMoving) { PointerMoving = pointerMoving; @@ -902,12 +903,12 @@ public async Task SelectTimeFromStick(int value, bool pointerMoving) StateHasChanged(); } - [JSInvokable] /// /// Handles a click on the clock pointer and advances or submits the time selection. /// /// The selected hour or minute. /// A task that represents the asynchronous operation. + [JSInvokable] public async Task OnStickClick(int value) { // The pointer is up and not moving so animations can be enabled again. From 270e1df8428b512dd9251be8f57d433719a00ae5 Mon Sep 17 00:00:00 2001 From: w3ori <57896209+w3ori@users.noreply.github.com> Date: Tue, 22 Sep 2026 10:03:01 +0200 Subject: [PATCH 21/22] open default DateTimeOffset values in current month --- .../DateTimePicker/MudBaseDatePickerX.cs | 13 ++++- .../DateTimePicker/MudDateTimePicker.razor.cs | 8 ++-- .../Components/DateTimePickerTests.cs | 47 +++++++++++++++++++ 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs index 44d94814..b1800cd7 100644 --- a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs +++ b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudBaseDatePickerX.cs @@ -250,6 +250,17 @@ public DateTime? PickerMonth /// protected internal bool IsDateOnly => (Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T)) == typeof(DateOnly); + /// + /// Determines whether a non-nullable picker value is its default value. + /// + /// The value to evaluate. + /// when is non-nullable and equals its default value; otherwise, . + protected internal bool IsDefaultValue(T? value) + { + return Nullable.GetUnderlyingType(typeof(T)) is null && + EqualityComparer.Default.Equals(value, default); + } + /// /// Generic conversion method to convert the generic type T to DateTime. Supports DateTime and DateTimeOffset. /// @@ -376,7 +387,7 @@ protected override async Task OnPickerOpenedAsync() var dateTime = ToDateTime(_value); - if (dateTime.HasValue && dateTime.Value == default) + if (IsDefaultValue(_value)) { var culture = GetCulture(); var calendar = culture.Calendar; diff --git a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs index 192394de..c7c3d9bc 100644 --- a/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs +++ b/src/CodeBeam.MudBlazor.Extensions/Components/DateTimePicker/MudDateTimePicker.razor.cs @@ -454,12 +454,14 @@ protected string GetTitleDateString() /// Gets the date used in the picker header. ///
/// - /// A non-nullable date picker uses as its cleared value. The header uses the - /// current local date as the picker reference date for that sentinel instead of presenting it as a selected value. + /// A non-nullable date picker uses its default value as its cleared value. The header uses the current local date + /// as the picker reference date for that sentinel instead of presenting it as a selected value. /// private DateTime GetPickerHeaderDate(DateTime? date) { - if (date is not null && date.Value != DateTime.MinValue) + var boundDate = ToDateTime(_value); + + if (date is not null && (!IsDefaultValue(_value) || date != boundDate)) return date.Value; return TimeProvider.GetLocalNow().Date; diff --git a/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs b/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs index 0fec0d9d..e740b0e1 100644 --- a/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs +++ b/tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/DateTimePickerTests.cs @@ -2,6 +2,8 @@ using Bunit; using Microsoft.AspNetCore.Components; +using System.Reflection; + namespace MudExtensions.UnitTests.Components; [TestFixture] @@ -98,4 +100,49 @@ public void DateTimePicker_Null_Value_Should_Render_Empty() var comp = RenderPicker(); comp.Find("input").GetAttribute("value").Should().BeNullOrEmpty(); } + + [Test] + public async Task DateTimePicker_DefaultDateTime_Should_Open_Current_Month() + { + await AssertDefaultValueOpensCurrentMonthAsync(); + } + + [Test] + public async Task DateTimePicker_DefaultDateOnly_Should_Open_Current_Month() + { + await AssertDefaultValueOpensCurrentMonthAsync(); + } + + [Test] + public async Task DateTimePicker_DefaultDateTimeOffset_WithIstanbulTimeZone_Should_Open_Current_Month() + { + var istanbulTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Europe/Istanbul"); + await AssertDefaultValueOpensCurrentMonthAsync(istanbulTimeZone); + } + + private async Task AssertDefaultValueOpensCurrentMonthAsync(TimeZoneInfo? timeZone = null) + { + var today = DateTime.Today; + var expectedMonth = new DateTime(today.Year, today.Month, 1); + var comp = Context.Render>(parameters => + { + parameters.Add(p => p.Value, default(T)); + + if (timeZone is not null) + parameters.Add(p => p.TimeZone, timeZone); + }); + + await InvokePickerOpenedAsync(comp.Instance); + + comp.Instance.PickerMonth.Should().Be(expectedMonth); + } + + private static Task InvokePickerOpenedAsync(MudDateTimePicker picker) + { + var method = typeof(MudBaseDatePickerX).GetMethod( + "OnPickerOpenedAsync", + BindingFlags.Instance | BindingFlags.NonPublic); + + return (Task)method!.Invoke(picker, null)!; + } } From e84b1071a2a032231c307393efccc0bd5dcce95e Mon Sep 17 00:00:00 2001 From: Tibor Konkolyi <57896209+w3ori@users.noreply.github.com> Date: Tue, 22 Sep 2026 10:07:23 +0200 Subject: [PATCH 22/22] Remove unnecessary blank lines in DateTimePickerExample2 --- .../DateTimePicker/Examples/DateTimePickerExample2.razor | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor b/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor index 25dc772c..2b8a39bf 100644 --- a/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor +++ b/docs/CodeBeam.MudBlazor.Extensions.Docs/Pages/Components/DateTimePicker/Examples/DateTimePickerExample2.razor @@ -44,5 +44,3 @@ private bool _clearable = false; private bool _editable = false; } - -