From c66db2e649d3d11e4cc05f8a25ec8d044c343471 Mon Sep 17 00:00:00 2001
From: Anu6is
Date: Sun, 16 Mar 2025 17:31:24 -0400
Subject: [PATCH 01/22] MudTextField: Fix AutoGrow Adornment padding (#10932)
---
.../TextField/TextFieldRenderTest.razor | 16 ++++++++++++
src/MudBlazor/Styles/components/_input.scss | 26 +++++++++----------
2 files changed, 29 insertions(+), 13 deletions(-)
create mode 100644 src/MudBlazor.UnitTests.Viewer/TestComponents/TextField/TextFieldRenderTest.razor
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/TextField/TextFieldRenderTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/TextField/TextFieldRenderTest.razor
new file mode 100644
index 000000000000..aeb35e9ef6db
--- /dev/null
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/TextField/TextFieldRenderTest.razor
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/src/MudBlazor/Styles/components/_input.scss b/src/MudBlazor/Styles/components/_input.scss
index df260c064e05..b795a92f12dd 100644
--- a/src/MudBlazor/Styles/components/_input.scss
+++ b/src/MudBlazor/Styles/components/_input.scss
@@ -370,7 +370,7 @@
}
&.mud-input-root-margin-dense {
- margin-top: 3px;
+ padding-top: 3px;
}
&.mud-input-root-type-search {
@@ -429,15 +429,15 @@
}
&.mud-input-root-adorned-start {
- margin-left: 0;
- margin-inline-start: 0;
- margin-inline-end: 12px;
+ padding-left: 0;
+ padding-inline-start: 0;
+ padding-inline-end: 12px;
}
&.mud-input-root-adorned-end {
- margin-right: 0;
- margin-inline-end: unset;
- margin-inline-start: 12px;
+ padding-right: 0;
+ padding-inline-end: unset;
+ padding-inline-start: 12px;
}
}
@@ -480,15 +480,15 @@
}
&.mud-input-root-adorned-start {
- margin-left: 0;
- margin-inline-start: 0;
- margin-inline-end: 14px;
+ padding-left: 0;
+ padding-inline-start: 0;
+ padding-inline-end: 14px;
}
&.mud-input-root-adorned-end {
- margin-right: 0;
- margin-inline-end: 0;
- margin-inline-start: 14px;
+ padding-right: 0;
+ padding-inline-end: 0;
+ padding-inline-start: 14px;
}
}
}
From 296f392b5e9787519d8659370c486f55109b0874 Mon Sep 17 00:00:00 2001
From: "Artyom M."
Date: Mon, 17 Mar 2025 11:54:57 +0200
Subject: [PATCH 02/22] MudInput: Fix trimming issue (#11029)
---
src/MudBlazor/Components/Input/MudInput.razor.cs | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/MudBlazor/Components/Input/MudInput.razor.cs b/src/MudBlazor/Components/Input/MudInput.razor.cs
index 784d0f280d74..f5dbad93b19c 100644
--- a/src/MudBlazor/Components/Input/MudInput.razor.cs
+++ b/src/MudBlazor/Components/Input/MudInput.razor.cs
@@ -1,4 +1,5 @@
-using Microsoft.AspNetCore.Components;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
using MudBlazor.Utilities;
@@ -18,6 +19,7 @@ public partial class MudInput : MudBaseInput
private ElementReference _elementReference1;
private readonly Lazy>> _dotNetReferenceLazy;
+ [DynamicDependency(nameof(CallOnBlurredAsync))]
public MudInput()
{
_dotNetReferenceLazy = new Lazy>>(DotNetObjectReference.Create(this));
@@ -386,7 +388,7 @@ public async Task CallOnBlurredAsync()
if (!_isFocused)
return;
- StateHasChanged();
+ await InvokeAsync(StateHasChanged);
await OnBlurredAsync(new FocusEventArgs { Type = "jsBlur.OnBlur" });
}
}
From 67b7be39b41695edb129b52ce6086f5fd1b476b0 Mon Sep 17 00:00:00 2001
From: Daniel Chalmers
Date: Mon, 17 Mar 2025 04:57:02 -0500
Subject: [PATCH 03/22] MudTable: Fix table sort label style (#11022)
---
src/MudBlazor.UnitTests.Viewer/Program.cs | 2 +
.../Table/TableSortLabelTest.razor | 38 ++++++++-------
.../Components/TableTests.cs | 47 +++++++++----------
.../Components/Table/MudTableSortLabel.razor | 11 +----
.../Table/MudTableSortLabel.razor.cs | 35 +++++---------
src/MudBlazor/Styles/components/_table.scss | 32 +++++++------
6 files changed, 79 insertions(+), 86 deletions(-)
diff --git a/src/MudBlazor.UnitTests.Viewer/Program.cs b/src/MudBlazor.UnitTests.Viewer/Program.cs
index 6d5939361867..40b47bfa326e 100644
--- a/src/MudBlazor.UnitTests.Viewer/Program.cs
+++ b/src/MudBlazor.UnitTests.Viewer/Program.cs
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
+using MudBlazor.Examples.Data;
using MudBlazor.Services;
namespace MudBlazor.UnitTests;
@@ -12,6 +13,7 @@ public static Task Main(string[] args)
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddMudServices();
+ builder.Services.AddSingleton();
return builder.Build().RunAsync();
}
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Table/TableSortLabelTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Table/TableSortLabelTest.razor
index f00c0446b3a5..141a789e4cc1 100644
--- a/src/MudBlazor.UnitTests.Viewer/TestComponents/Table/TableSortLabelTest.razor
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Table/TableSortLabelTest.razor
@@ -1,27 +1,33 @@
-
+@using System.Net.Http.Json
+@using MudBlazor.Examples.Data
+@using MudBlazor.Examples.Data.Models
+@inject IPeriodicTableService _periodicTableService
+
+
- Sortable Header
+ Nr
+ Unsortable Nr
+ Position
+ Mass
- @context
+ @context.Number
+ @context.Number
+ @context.Position
+ @context.Molar
+
+
+
-Sort Enabled
-
@code {
- public static string __description__ = "Table sortable header styles should not be applied when sorting is disabled.";
-
- private string[] _data = ["A", "B", "C"];
- private bool _sortEnabled;
-
- [Parameter]
- public bool SortEnabled { get; set; } = true;
+ private IEnumerable _elements = new List();
- protected override void OnParametersSet()
+ protected override async Task OnInitializedAsync()
{
- base.OnParametersSet();
-
- _sortEnabled = SortEnabled;
+ _elements = await _periodicTableService.GetElements();
}
}
+
+
diff --git a/src/MudBlazor.UnitTests/Components/TableTests.cs b/src/MudBlazor.UnitTests/Components/TableTests.cs
index de3b20cb4e09..2b0cbcc8bd28 100644
--- a/src/MudBlazor.UnitTests/Components/TableTests.cs
+++ b/src/MudBlazor.UnitTests/Components/TableTests.cs
@@ -115,24 +115,6 @@ public async Task TableDisabledSort()
comp.FindAll("td")[2].TextContent.Trim().Should().Be("A");
}
- [Theory]
- [TestCase(true)]
- [TestCase(false)]
- public void TableSortLabel(bool sortEnabled)
- {
- // Arrange
- var comp = Context.RenderComponent(
- parameters => parameters.Add(x => x.SortEnabled, sortEnabled));
- var tableSortLabel = comp.FindComponent>();
-
- // Assert
- tableSortLabel
- .Find("span")
- .GetAttribute("class")
- .Contains("mud-button-root")
- .Should().Be(sortEnabled);
- }
-
///
/// Check if the loading parameter is adding a supplementary row.
///
@@ -1315,25 +1297,25 @@ public void TableServerSideDataTest6()
var comp = Context.RenderComponent();
comp.Find("#counter").TextContent.Should().Be("1"); //initial counter
- comp.Find("span.mud-button-root.mud-table-sort-label").Click(); // sort
+ comp.Find("span.mud-clickable.mud-table-sort-label").Click(); // sort
comp.Find("#counter").TextContent.Should().Be("2");
- comp.Find("span.mud-button-root.mud-table-sort-label").Click(); // sort
+ comp.Find("span.mud-clickable.mud-table-sort-label").Click(); // sort
comp.Find("#counter").TextContent.Should().Be("3");
- comp.Find("span.mud-button-root.mud-table-sort-label").Click(); // sort
+ comp.Find("span.mud-clickable.mud-table-sort-label").Click(); // sort
comp.Find("#counter").TextContent.Should().Be("4");
comp.Find("#reseter").Click(); //reset counter and test again
comp.Find("#counter").TextContent.Should().Be("0");
- comp.Find("span.mud-button-root.mud-table-sort-label").Click(); // sort
+ comp.Find("span.mud-clickable.mud-table-sort-label").Click(); // sort
comp.Find("#counter").TextContent.Should().Be("1");
- comp.Find("span.mud-button-root.mud-table-sort-label").Click(); // sort
+ comp.Find("span.mud-clickable.mud-table-sort-label").Click(); // sort
comp.Find("#counter").TextContent.Should().Be("2");
- comp.Find("span.mud-button-root.mud-table-sort-label").Click(); // sort
+ comp.Find("span.mud-clickable.mud-table-sort-label").Click(); // sort
comp.Find("#counter").TextContent.Should().Be("3");
}
@@ -2537,5 +2519,22 @@ public async Task TestCurrentPageParameterTwoWayBinding()
comp.WaitForAssertion(() => table.CurrentPage.Should().Be(2));
comp.WaitForAssertion(() => comp.Find(".mud-table-body .mud-table-row .mud-table-cell").TextContent.Should().Be("3"));
}
+
+ [Test]
+ [TestCase(SortDirection.None)]
+ [TestCase(SortDirection.Ascending)]
+ [TestCase(SortDirection.Descending)]
+ public void TableSortLabelDirectionClasses(SortDirection direction)
+ {
+ var comp = Context.RenderComponent>(parameters => parameters
+ .Add(p => p.SortDirection, direction)
+ );
+
+ var icon = comp.Find(".mud-table-sort-label-icon");
+
+ icon.ClassList.Should().Contain("mud-table-sort-label-icon");
+ icon.ClassList.Contains("mud-direction-asc").Should().Be(direction == SortDirection.Ascending);
+ icon.ClassList.Contains("mud-direction-desc").Should().Be(direction == SortDirection.Descending);
+ }
}
}
diff --git a/src/MudBlazor/Components/Table/MudTableSortLabel.razor b/src/MudBlazor/Components/Table/MudTableSortLabel.razor
index 8254c9f07ba7..9c72cd030eb0 100644
--- a/src/MudBlazor/Components/Table/MudTableSortLabel.razor
+++ b/src/MudBlazor/Components/Table/MudTableSortLabel.razor
@@ -3,21 +3,14 @@
@inherits MudComponentBase
@implements IDisposable
-
+
@if (!AppendIcon)
{
@ChildContent
}
@if (Enabled)
{
- @if (_direction != SortDirection.None)
- {
-
- }
- else
- {
-
- }
+
}
@if (AppendIcon)
{
diff --git a/src/MudBlazor/Components/Table/MudTableSortLabel.razor.cs b/src/MudBlazor/Components/Table/MudTableSortLabel.razor.cs
index 5023c5de88f0..e2cf711ed040 100644
--- a/src/MudBlazor/Components/Table/MudTableSortLabel.razor.cs
+++ b/src/MudBlazor/Components/Table/MudTableSortLabel.razor.cs
@@ -1,6 +1,4 @@
-using System;
-using System.Diagnostics.CodeAnalysis;
-using System.Threading.Tasks;
+using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Components;
using MudBlazor.Utilities;
@@ -16,10 +14,18 @@ public partial class MudTableSortLabel<[DynamicallyAccessedMembers(DynamicallyAc
{
private SortDirection _direction = SortDirection.None;
- protected string Classname => new CssBuilder("mud-table-sort-label")
- .AddClass("mud-button-root", Enabled)
- .AddClass(Class)
- .Build();
+ protected string Classname =>
+ new CssBuilder("mud-table-sort-label")
+ .AddClass("mud-clickable", Enabled)
+ .AddClass(Class)
+ .Build();
+
+ protected string SortIconClassname =>
+ new CssBuilder("mud-table-sort-label-icon")
+ .AddClass("mud-direction-none", _direction == SortDirection.None)
+ .AddClass("mud-direction-asc", _direction == SortDirection.Ascending)
+ .AddClass("mud-direction-desc", _direction == SortDirection.Descending)
+ .Build();
///
/// The current state of the containing this sort label.
@@ -173,20 +179,5 @@ internal void SetSortDirection(SortDirection dir)
_direction = dir;
StateHasChanged();
}
-
- private string GetSortIconClass()
- {
- if (_direction == SortDirection.Descending)
- {
- return "mud-table-sort-label-icon mud-direction-desc";
- }
-
- if (_direction == SortDirection.Ascending)
- {
- return "mud-table-sort-label-icon mud-direction-asc";
- }
-
- return "mud-table-sort-label-icon";
- }
}
}
diff --git a/src/MudBlazor/Styles/components/_table.scss b/src/MudBlazor/Styles/components/_table.scss
index 1ba5f58d0704..24613149afb3 100644
--- a/src/MudBlazor/Styles/components/_table.scss
+++ b/src/MudBlazor/Styles/components/_table.scss
@@ -31,18 +31,6 @@
color: var(--mud-palette-text-primary);
font-weight: 500;
line-height: 1.5rem;
-
- & .mud-button-root {
- @media(hover: hover) and (pointer: fine) {
- &:hover {
- color: var(--mud-palette-action-default);
-
- .mud-table-sort-label-icon {
- opacity: 0.8;
- }
- }
- }
- }
}
}
@@ -65,15 +53,30 @@
}
.mud-table-sort-label {
- user-select: auto;
display: inline-flex;
align-items: center;
flex-direction: inherit;
justify-content: flex-start;
+ &.mud-clickable {
+ cursor: pointer;
+ user-select: none;
+ transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
+
+ @media(hover: hover) and (pointer: fine) {
+ &:hover {
+ opacity: 0.64;
+
+ .mud-table-sort-label-icon {
+ opacity: 1;
+ }
+ }
+ }
+ }
+
.mud-table-sort-label-icon {
font-size: 18px;
- transition: opacity 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,transform 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
+ transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,transform 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
margin-left: 4px;
user-select: none;
margin-right: 4px;
@@ -101,7 +104,6 @@
}
-
.mud-table-cell {
display: table-cell;
padding: 16px;
From 938d4bc68117f00439502d076ad2742af5a82d51 Mon Sep 17 00:00:00 2001
From: Anu6is
Date: Mon, 17 Mar 2025 13:20:04 -0400
Subject: [PATCH 04/22] MudDateRangePicker: Fix error with MaxDays and
IsDateDisabledFunc (#11033)
---
.../DateRangePicker/DateRangePickerPage.razor | 4 +++-
.../Components/DateRangePickerTests.cs | 18 ++++++++++++++++++
.../DatePicker/MudDateRangePicker.razor.cs | 12 +++++++++++-
3 files changed, 32 insertions(+), 2 deletions(-)
diff --git a/src/MudBlazor.Docs/Pages/Components/DateRangePicker/DateRangePickerPage.razor b/src/MudBlazor.Docs/Pages/Components/DateRangePicker/DateRangePickerPage.razor
index a599cd422907..aa8eba137383 100644
--- a/src/MudBlazor.Docs/Pages/Components/DateRangePicker/DateRangePickerPage.razor
+++ b/src/MudBlazor.Docs/Pages/Components/DateRangePicker/DateRangePickerPage.razor
@@ -39,7 +39,9 @@
- Defines the smallest and largest number of days that a user can select
+ Defines the smallest and largest number of days that a user can select.
+ For performance reasons, the component checks up to 50 years ahead, from the user-selected start date, to find valid dates that meet the MaxDays requirement.
+ To extend this limit, set the MaxDate property to a specific date.
diff --git a/src/MudBlazor.UnitTests/Components/DateRangePickerTests.cs b/src/MudBlazor.UnitTests/Components/DateRangePickerTests.cs
index b7e249cb2c94..1baeadf8075f 100644
--- a/src/MudBlazor.UnitTests/Components/DateRangePickerTests.cs
+++ b/src/MudBlazor.UnitTests/Components/DateRangePickerTests.cs
@@ -1183,7 +1183,25 @@ public async Task DateRangePicker_MinMaxDays()
await comp.FindAll("button.mud-picker-calendar-day").Where(x => x.TrimmedText().Equals("24")).First().ClickAsync(new MouseEventArgs());
comp.Instance.DateRange.Should().Be(new DateRange(new DateTime(2025, 1, 16).Date, new DateTime(2025, 1, 24).Date)); //max valid range 9 days
+ }
+
+ [Test]
+ public async Task DateRangePicker_MaxSelectableDateTest()
+ {
+ var comp = Context.RenderComponent();
+ comp.SetParametersAndRender(parameters => parameters.Add(picker => picker.MaxDays, 30)
+ .Add(picker => picker.PickerVariant, PickerVariant.Static)
+ .Add(picker => picker.IsDateDisabledFunc, x => x.Date > DateTime.Today));
+
+ var today = DateTime.Today;
+
+ await comp.FindAll("button.mud-picker-calendar-day")
+ .Where(x => x.TrimmedText().Equals(today.Day.ToString())).First().ClickAsync(new MouseEventArgs());
+ await comp.FindAll("button.mud-picker-calendar-day")
+ .Where(x => x.TrimmedText().Equals(today.Day.ToString())).First().ClickAsync(new MouseEventArgs());
+
+ comp.Instance.DateRange.Start.Should().Be(comp.Instance.DateRange.End);
}
}
}
diff --git a/src/MudBlazor/Components/DatePicker/MudDateRangePicker.razor.cs b/src/MudBlazor/Components/DatePicker/MudDateRangePicker.razor.cs
index c097137ed443..9be9b1091b4c 100644
--- a/src/MudBlazor/Components/DatePicker/MudDateRangePicker.razor.cs
+++ b/src/MudBlazor/Components/DatePicker/MudDateRangePicker.razor.cs
@@ -285,20 +285,30 @@ private static bool IsDateOutOfRange(DateTime date, DateTime selectedDate, DateR
private DateTime GetMaxSelectableDate(DateTime startDate, int maxDays)
{
var validDayCount = 1;
+ var lastValidDate = startDate;
var maxDate = startDate.AddDays(1);
while (validDayCount < maxDays)
{
if (!IsDateDisabledFunc(maxDate))
+ {
validDayCount++;
+ lastValidDate = maxDate;
+ }
if (validDayCount == maxDays)
break;
+ if (maxDate.Date > MaxDate.GetValueOrDefault(startDate.AddYears(50)).Date)
+ break;
+
+ if (maxDate.Date == DateTime.MaxValue.Date)
+ break;
+
maxDate = maxDate.AddDays(1);
}
- return maxDate;
+ return lastValidDate;
}
///
From 7a1d720df2dc40368a8d4beaee3fa1e771a2a728 Mon Sep 17 00:00:00 2001
From: Daniel Chalmers
Date: Mon, 17 Mar 2025 17:32:05 -0500
Subject: [PATCH 05/22] MudPopover: Remove shadow if the popover is empty
(#11040)
---
src/MudBlazor/Styles/components/_popover.scss | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/MudBlazor/Styles/components/_popover.scss b/src/MudBlazor/Styles/components/_popover.scss
index 8ddd8aa6b43e..8294cff79cea 100644
--- a/src/MudBlazor/Styles/components/_popover.scss
+++ b/src/MudBlazor/Styles/components/_popover.scss
@@ -23,6 +23,10 @@
transition-delay: 0ms !important;
}
+ &:empty {
+ box-shadow: none !important;
+ }
+
.mud-list {
max-height: inherit;
overflow-y: auto;
@@ -60,7 +64,7 @@
}
.mud-select {
- .mud-popover-cascading-value {
+ .mud-popover-cascading-value {
z-index: calc(var(--mud-zindex-select) + 5);
}
}
From 05151e22d599d76ae5e7bca756de809dae8fdfbc Mon Sep 17 00:00:00 2001
From: Daniel Chalmers
Date: Tue, 18 Mar 2025 11:46:28 -0500
Subject: [PATCH 06/22] MudAutocomplete: Fix duplicate search calls when opened
on click (#11023)
---
.../Autocomplete/MudAutocomplete.razor.cs | 18 +++++++-----------
1 file changed, 7 insertions(+), 11 deletions(-)
diff --git a/src/MudBlazor/Components/Autocomplete/MudAutocomplete.razor.cs b/src/MudBlazor/Components/Autocomplete/MudAutocomplete.razor.cs
index d32ae8e2ec22..e209e808db20 100644
--- a/src/MudBlazor/Components/Autocomplete/MudAutocomplete.razor.cs
+++ b/src/MudBlazor/Components/Autocomplete/MudAutocomplete.razor.cs
@@ -24,6 +24,7 @@ public partial class MudAutocomplete : MudBaseInput
private int _elementKey = 0;
private int _returnedItemsCount;
private bool _open;
+ private bool _opening;
private MudInput _elementReference = null!;
private CancellationTokenSource? _cancellationTokenSrc;
private Task? _currentSearchTask;
@@ -665,11 +666,8 @@ public async Task CloseMenuAsync()
}
///
- /// Opens the drop-down of items.
+ /// Opens the drop-down of items, or refreshes the list if it is already open.
///
- ///
- /// Will have no effect if the autocomplete is disabled or read-only.
- ///
public async Task OpenMenuAsync()
{
if (MinCharacters > 0 && (string.IsNullOrWhiteSpace(Text) || Text.Length < MinCharacters))
@@ -679,6 +677,8 @@ public async Task OpenMenuAsync()
return;
}
+ _opening = true;
+
var searchedItems = Array.Empty();
CancelToken();
@@ -744,6 +744,7 @@ public async Task OpenMenuAsync()
Open = true;
}
+ _opening = false;
StateHasChanged();
}
@@ -964,17 +965,12 @@ private async Task OnInputActivationAsync(bool openMenu)
{
_isFocused = true;
- if (Open || GetDisabledState() || GetReadOnlyState())
- {
- return;
- }
-
- if (SelectOnActivation)
+ if (SelectOnActivation && !GetDisabledState() && !GetReadOnlyState())
{
await SelectAsync();
}
- if (openMenu)
+ if (openMenu && !Open && !_opening)
{
await OpenMenuAsync();
}
From fa9ba33d42c03711220a0312616ca0ea28f158f2 Mon Sep 17 00:00:00 2001
From: Versile Johnson II <148913404+versile2@users.noreply.github.com>
Date: Thu, 20 Mar 2025 05:20:14 -0500
Subject: [PATCH 07/22] MudPopover: Overlay Position Exclusion (#11038)
---
src/MudBlazor/TScripts/mudPopover.js | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/MudBlazor/TScripts/mudPopover.js b/src/MudBlazor/TScripts/mudPopover.js
index 249450c9625f..8181b1a69407 100644
--- a/src/MudBlazor/TScripts/mudPopover.js
+++ b/src/MudBlazor/TScripts/mudPopover.js
@@ -427,6 +427,10 @@ window.mudpopoverHelper = {
},
updatePopoverOverlay: function (popoverContentNode) {
+ // tooltips don't have an overlay
+ if (popoverContentNode.classList.contains("mud-tooltip")) {
+ return;
+ }
// set any associated overlay to equal z-index
const provider = popoverContentNode.closest('.mud-popover-provider');
if (provider && popoverContentNode.classList.contains("mud-popover")) {
From 0943a122857d94d4ca13669121b68d9cbb0d47b8 Mon Sep 17 00:00:00 2001
From: "Artyom M."
Date: Thu, 20 Mar 2025 15:18:04 +0200
Subject: [PATCH 08/22] ObserverManager: Optimize to reduce dictionary lookups
(#11067)
---
.../ObserverManager/ObserverManagerTests.cs | 36 +++++++++++
.../Extensions/ResizeOptionsExtensions.cs | 3 -
.../Browser/BrowserViewportService.cs | 12 +---
.../KeyInterceptor/KeyInterceptorService.cs | 12 +---
.../ObserverManager/ObserverManager.cs | 59 +++++++++++++------
5 files changed, 83 insertions(+), 39 deletions(-)
diff --git a/src/MudBlazor.UnitTests/Utilities/ObserverManager/ObserverManagerTests.cs b/src/MudBlazor.UnitTests/Utilities/ObserverManager/ObserverManagerTests.cs
index 496dd93901cd..a0a7beff5b34 100644
--- a/src/MudBlazor.UnitTests/Utilities/ObserverManager/ObserverManagerTests.cs
+++ b/src/MudBlazor.UnitTests/Utilities/ObserverManager/ObserverManagerTests.cs
@@ -65,6 +65,42 @@ public void TryGetSubscription_ReturnsFalseAndDefault_WhenObserverDoesNotExist()
retrievedObserver.Should().BeNull();
}
+ [Test]
+ public void TryGetOrAddSubscription_ReturnsTrueAndUpdatesObserver_WhenObserverExists()
+ {
+ // Arrange
+ var id = 1;
+ var observer1 = "Observer1";
+ var observer2 = "Observer2";
+ _observerManager.Subscribe(id, observer1);
+
+ // Act
+ var result = _observerManager.TryGetOrAddSubscription(id, observer2, out var newObserver);
+
+ // Assert
+ result.Should().BeTrue();
+ newObserver.Should().Be(observer2);
+ _observerManager.Count.Should().Be(1);
+ _observerManager.Observers[id].Should().Be(observer2);
+ }
+
+ [Test]
+ public void TryGetOrAddSubscription_ReturnsFalseAndAddsObserver_WhenObserverDoesNotExist()
+ {
+ // Arrange
+ var id = 1;
+ var observer = "Observer1";
+
+ // Act
+ var result = _observerManager.TryGetOrAddSubscription(id, observer, out var newObserver);
+
+ // Assert
+ result.Should().BeFalse();
+ newObserver.Should().Be(observer);
+ _observerManager.Count.Should().Be(1);
+ _observerManager.Observers[id].Should().Be(observer);
+ }
+
[Test]
public void FindObserverIdentities_ReturnsMatchingIdentities()
{
diff --git a/src/MudBlazor/Extensions/ResizeOptionsExtensions.cs b/src/MudBlazor/Extensions/ResizeOptionsExtensions.cs
index cc27300c4f1f..3f4219d7d78b 100644
--- a/src/MudBlazor/Extensions/ResizeOptionsExtensions.cs
+++ b/src/MudBlazor/Extensions/ResizeOptionsExtensions.cs
@@ -2,9 +2,6 @@
// MudBlazor licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using System;
-using System.Collections.Generic;
-using System.Linq;
using MudBlazor.Services;
namespace MudBlazor.Extensions;
diff --git a/src/MudBlazor/Services/Browser/BrowserViewportService.cs b/src/MudBlazor/Services/Browser/BrowserViewportService.cs
index cbb4c1510d90..16ff3effeb1b 100644
--- a/src/MudBlazor/Services/Browser/BrowserViewportService.cs
+++ b/src/MudBlazor/Services/Browser/BrowserViewportService.cs
@@ -101,22 +101,16 @@ public async Task SubscribeAsync(IBrowserViewportObserver observer, bool fireImm
optionsClone.BreakpointDefinitions = BreakpointGlobalOptions.GetDefaultOrUserDefinedBreakpointDefinition(optionsClone, ResizeOptions);
var subscription = await CreateJavaScriptListener(optionsClone, observer.Id);
- if (_observerManager.IsSubscribed(subscription))
- {
- // Only re-subscribe
- _observerManager.Subscribe(subscription, observer);
- }
- else
+
+ if (!_observerManager.TryGetOrAddSubscription(subscription, observer, out var newObserver))
{
- // Subscribe and fire if necessary
- _observerManager.Subscribe(subscription, observer);
if (fireImmediately)
{
// Not waiting for Browser Size to change and RaiseOnResized to fire and post event with current breakpoint and browser window size
var latestWindowSize = await GetCurrentBrowserWindowSizeAsync();
var latestBreakpoint = await GetCurrentBreakpointAsync();
// Notify only current subscription
- await observer.NotifyBrowserViewportChangeAsync(new BrowserViewportEventArgs(subscription.JavaScriptListenerId, latestWindowSize, latestBreakpoint, isImmediate: true));
+ await newObserver.NotifyBrowserViewportChangeAsync(new BrowserViewportEventArgs(subscription.JavaScriptListenerId, latestWindowSize, latestBreakpoint, isImmediate: true));
}
}
}
diff --git a/src/MudBlazor/Services/KeyInterceptor/KeyInterceptorService.cs b/src/MudBlazor/Services/KeyInterceptor/KeyInterceptorService.cs
index 0308e12b124e..7eecf6389578 100644
--- a/src/MudBlazor/Services/KeyInterceptor/KeyInterceptorService.cs
+++ b/src/MudBlazor/Services/KeyInterceptor/KeyInterceptorService.cs
@@ -54,17 +54,9 @@ public async Task SubscribeAsync(IKeyInterceptorObserver observer, KeyIntercepto
return;
}
- if (!_observerManager.IsSubscribed(observer.ElementId))
+ if (!_observerManager.TryGetOrAddSubscription(observer.ElementId, observer, out var newObserver))
{
- var isConnected = await _keyInterceptorInterop.Connect(_dotNetReferenceLazy.Value, observer.ElementId, options);
- if (isConnected)
- {
- _observerManager.Subscribe(observer.ElementId, observer);
- }
- }
- else
- {
- _observerManager.Subscribe(observer.ElementId, observer);
+ _ = await _keyInterceptorInterop.Connect(_dotNetReferenceLazy.Value, newObserver.ElementId, options);
}
}
diff --git a/src/MudBlazor/Utilities/ObserverManager/ObserverManager.cs b/src/MudBlazor/Utilities/ObserverManager/ObserverManager.cs
index ed76188cd1b2..501c2a1aa1ce 100644
--- a/src/MudBlazor/Utilities/ObserverManager/ObserverManager.cs
+++ b/src/MudBlazor/Utilities/ObserverManager/ObserverManager.cs
@@ -25,15 +25,23 @@ namespace MudBlazor.Utilities.ObserverManager;
///
internal class ObserverManager : IEnumerable where TIdentity : notnull
{
- private readonly ConcurrentDictionary _observers = new();
+ private readonly ConcurrentDictionary _observers;
private readonly ILogger _log;
///
/// Initializes a new instance of the class.
///
- public ObserverManager(ILogger log)
+ public ObserverManager(ILogger log) : this(log, null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ObserverManager(ILogger log, IEqualityComparer? comparer)
{
_log = log ?? throw new ArgumentNullException(nameof(log));
+ _observers = new(comparer);
}
///
@@ -84,16 +92,13 @@ public IEnumerable FindObserverIdentities(Func predicate(kvp.Key, kvp.Value.Observer)).Select(kvp => kvp.Key);
///
- /// Ensures that the provided is subscribed, renewing its subscription.
+ /// Tries to get the existing subscription for the specified identity, or subscribes the observer if it does not exist.
///
- ///
- /// The observer's identity.
- ///
- ///
- /// The observer.
- ///
- /// A delegate callback throws an exception.
- public void Subscribe(TIdentity id, TObserver observer)
+ /// The identity of the observer.
+ /// The observer to subscribe if it does not already exist.
+ /// When this method returns, contains the observer associated with the specified identity, whether it was already subscribed or newly subscribed.
+ /// True if the observer was already subscribed; otherwise, false.
+ public bool TryGetOrAddSubscription(TIdentity id, TObserver observer, out TObserver newObserver)
{
// Add or update the subscription.
if (_observers.TryGetValue(id, out var entry))
@@ -103,15 +108,35 @@ public void Subscribe(TIdentity id, TObserver observer)
{
_log.LogTrace("Updating entry for {Id}/{Observer}. {Count} total observers.", id, observer, _observers.Count);
}
+
+ newObserver = entry.Observer;
+ return true;
}
- else
+
+ var newEntry = new ObserverEntry(observer);
+ _observers[id] = newEntry;
+ if (_log.IsEnabled(LogLevel.Trace))
{
- _observers[id] = new ObserverEntry(observer);
- if (_log.IsEnabled(LogLevel.Trace))
- {
- _log.LogTrace("Adding entry for {Id}/{Observer}. {Count} total observers after add.", id, observer, _observers.Count);
- }
+ _log.LogTrace("Adding entry for {Id}/{Observer}. {Count} total observers after add.", id, observer, _observers.Count);
}
+
+ newObserver = newEntry.Observer;
+ return false;
+ }
+
+ ///
+ /// Ensures that the provided is subscribed, renewing its subscription.
+ ///
+ ///
+ /// The observer's identity.
+ ///
+ ///
+ /// The observer.
+ ///
+ /// A delegate callback throws an exception.
+ public void Subscribe(TIdentity id, TObserver observer)
+ {
+ _ = TryGetOrAddSubscription(id, observer, out _);
}
///
From aed57bc6a9808822968ee73dbe944ef3c3a8d4a4 Mon Sep 17 00:00:00 2001
From: Tomas Green
Date: Fri, 21 Mar 2025 01:29:55 +0100
Subject: [PATCH 09/22] DataGrid: Mutlisort key fix on mac (#11070)
---
src/MudBlazor/Components/DataGrid/HeaderCell.razor.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/MudBlazor/Components/DataGrid/HeaderCell.razor.cs b/src/MudBlazor/Components/DataGrid/HeaderCell.razor.cs
index c7b8c43c4c0b..ced148ee1d38 100644
--- a/src/MudBlazor/Components/DataGrid/HeaderCell.razor.cs
+++ b/src/MudBlazor/Components/DataGrid/HeaderCell.razor.cs
@@ -356,7 +356,7 @@ internal async Task SortChangedAsync(MouseEventArgs args)
DataGrid.DropContainerHasChanged();
- if (args.CtrlKey && DataGrid.SortMode == SortMode.Multiple)
+ if ((args.MetaKey || args.CtrlKey) && DataGrid.SortMode == SortMode.Multiple)
await InvokeAsync(() => DataGrid.ExtendSortAsync(Column.PropertyName, SortDirection, Column.GetLocalSortFunc(), Column.Comparer));
else
await InvokeAsync(() => DataGrid.SetSortAsync(Column.PropertyName, SortDirection, Column.GetLocalSortFunc(), Column.Comparer));
From 68a2118f2f479a984fe1d41c7251d5b8a8433a24 Mon Sep 17 00:00:00 2001
From: Tim Macintyre <103250605+timmac-qmc@users.noreply.github.com>
Date: Fri, 21 Mar 2025 11:24:47 +0000
Subject: [PATCH 10/22] MudDialog : Duplicate dialog fix (#11048)
---
.../Dialog/InlineDialogDuplicateTest.razor | 27 +++++++
.../Components/DialogTests.cs | 16 ++++
.../Components/Dialog/MudDialog.razor.cs | 75 ++++++++++---------
3 files changed, 84 insertions(+), 34 deletions(-)
create mode 100644 src/MudBlazor.UnitTests.Viewer/TestComponents/Dialog/InlineDialogDuplicateTest.razor
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Dialog/InlineDialogDuplicateTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Dialog/InlineDialogDuplicateTest.razor
new file mode 100644
index 000000000000..f198ba370ae3
--- /dev/null
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Dialog/InlineDialogDuplicateTest.razor
@@ -0,0 +1,27 @@
+
+
+ Open dialog
+
+
+
+
+
+
+ Edit rating
+
+
+
+ There should only be one dialog
+
+
+
+
+
+@code {
+ private bool _visible;
+
+ private void OpenDialog() {
+ _visible = true;
+ StateHasChanged();
+ }
+}
diff --git a/src/MudBlazor.UnitTests/Components/DialogTests.cs b/src/MudBlazor.UnitTests/Components/DialogTests.cs
index 6a855894d84a..d76adfcd0a9b 100644
--- a/src/MudBlazor.UnitTests/Components/DialogTests.cs
+++ b/src/MudBlazor.UnitTests/Components/DialogTests.cs
@@ -1325,6 +1325,22 @@ public async Task DialogWithNestedDialogOptionShouldNotReset()
// Close button should still be visible
closeButton().Should().NotBeNull();
}
+
+ ///
+ /// Ensures dialog will only be shown once
+ ///
+ [Test]
+ public async Task DuplicateDialogTest()
+ {
+ var comp = Context.RenderComponent();
+ var service = Context.Services.GetRequiredService();
+ service.Should().NotBe(null);
+ var comp1 = Context.RenderComponent();
+ // open the dialog
+ comp1.Find("button").Click();
+ await Task.Delay(1000);
+ comp.FindComponents().Count.Should().Be(1);
+ }
}
internal class CustomDialogService : DialogService
diff --git a/src/MudBlazor/Components/Dialog/MudDialog.razor.cs b/src/MudBlazor/Components/Dialog/MudDialog.razor.cs
index 3acfe99230c8..683bd7b335a4 100644
--- a/src/MudBlazor/Components/Dialog/MudDialog.razor.cs
+++ b/src/MudBlazor/Components/Dialog/MudDialog.razor.cs
@@ -2,6 +2,7 @@
// Copyright (c) 2019 Blazored - See https://github.com/Blazored
// Copyright (c) 2020 MudBlazor Contributors
+using System.Threading;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using MudBlazor.Interfaces;
@@ -25,6 +26,7 @@ public partial class MudDialog : MudComponentBase
{
private IDialogReference? _reference;
private readonly ParameterState _visibleState;
+ private SemaphoreSlim _showLock = new SemaphoreSlim(1, 1);
///
/// Creates a new instance.
@@ -195,44 +197,49 @@ public MudDialog()
/// The reference to the displayed instance of this dialog.
public async Task ShowAsync(string? title = null, DialogOptions? options = null)
{
- if (!IsInline)
+ await _showLock.WaitAsync();
+ try
{
- throw new InvalidOperationException("You can only show an inlined dialog.");
- }
+ if (!IsInline)
+ {
+ throw new InvalidOperationException("You can only show an inlined dialog.");
+ }
- if (_reference is not null)
- {
- await CloseAsync();
- }
+ if (_reference is not null)
+ return _reference;
- var parameters = new DialogParameters
- {
- [nameof(Class)] = Class,
- [nameof(Style)] = Style,
- [nameof(Tag)] = Tag,
- [nameof(UserAttributes)] = UserAttributes,
- [nameof(TitleContent)] = TitleContent,
- [nameof(DialogContent)] = DialogContent,
- [nameof(DialogActions)] = DialogActions,
- [nameof(OnBackdropClick)] = OnBackdropClick,
- [nameof(Gutters)] = Gutters,
- [nameof(TitleClass)] = TitleClass,
- [nameof(ContentClass)] = ContentClass,
- [nameof(ActionsClass)] = ActionsClass,
- [nameof(ContentStyle)] = ContentStyle,
- [nameof(DefaultFocus)] = DefaultFocus,
- };
-
- _reference = await DialogService.ShowAsync(title, parameters, options ?? Options);
-
- await _visibleState.SetValueAsync(true);
-
- // Do not await this!
- _reference.Result.ContinueWith(t =>
+ var parameters = new DialogParameters
+ {
+ [nameof(Class)] = Class,
+ [nameof(Style)] = Style,
+ [nameof(Tag)] = Tag,
+ [nameof(UserAttributes)] = UserAttributes,
+ [nameof(TitleContent)] = TitleContent,
+ [nameof(DialogContent)] = DialogContent,
+ [nameof(DialogActions)] = DialogActions,
+ [nameof(OnBackdropClick)] = OnBackdropClick,
+ [nameof(Gutters)] = Gutters,
+ [nameof(TitleClass)] = TitleClass,
+ [nameof(ContentClass)] = ContentClass,
+ [nameof(ActionsClass)] = ActionsClass,
+ [nameof(ContentStyle)] = ContentStyle,
+ [nameof(DefaultFocus)] = DefaultFocus,
+ };
+
+ _reference = await DialogService.ShowAsync(title, parameters, options ?? Options);
+
+ await _visibleState.SetValueAsync(true);
+
+ // Do not await this!
+ _reference.Result.ContinueWith(t =>
+ {
+ return InvokeAsync(() => _visibleState.SetValueAsync(false));
+ }).CatchAndLog();
+ }
+ finally
{
- return InvokeAsync(() => _visibleState.SetValueAsync(false));
- }).CatchAndLog();
-
+ _showLock.Release();
+ }
return _reference;
}
From 22e14b65f729cdb25b23c6e31582dacd6f8a759d Mon Sep 17 00:00:00 2001
From: Anu6is
Date: Sat, 22 Mar 2025 10:55:11 -0400
Subject: [PATCH 11/22] MudSelect: Add FitContent Parameter (#10894)
Co-authored-by: Artyom M.
---
.../Examples/SelectFitContentExample.razor | 51 +++++++++++++++++++
.../Examples/SelectPlaygroundExample.razor | 23 +++++----
.../Pages/Components/Select/SelectPage.razor | 31 +++++++++++
.../MultiSelectCustomizedTextTest.razor | 2 +-
.../Select/MultiSelectTest2.razor | 2 +-
.../Select/MultiSelectTest5.razor | 4 +-
.../Select/MultiSelectTest7.razor | 2 +-
.../Select/MultiSelectTestRequiredValue.razor | 1 -
...=> MultiSelectWithInitialValuesTest.razor} | 4 +-
.../MultiSelectWithValueContainZeroTest.razor | 2 +-
.../Select/ReloadSelectItemsTest.razor | 4 +-
.../Select/ReselectValueTest.razor | 2 +-
.../Select/SelectFitContentTest.razor | 47 +++++++++++++++++
.../Select/SelectFocusAndTypeTest.razor | 2 +-
.../Select/SelectRequiredTest.razor | 2 +-
.../Select/SelectScrollDrawerTest.razor | 2 +-
.../TestComponents/Select/SelectTest1.razor | 22 ++++----
.../Select/SelectValidationDataAttrTest.razor | 3 +-
.../Select/SelectVariantsTest.razor | 2 +-
.../Components/SelectTests.cs | 39 +++++++++++++-
.../Components/Select/MudSelect.razor | 34 ++++++++++++-
.../Components/Select/MudSelect.razor.cs | 34 +++++++++++++
src/MudBlazor/Styles/components/_select.scss | 9 ++++
23 files changed, 283 insertions(+), 41 deletions(-)
create mode 100644 src/MudBlazor.Docs/Pages/Components/Select/Examples/SelectFitContentExample.razor
rename src/MudBlazor.UnitTests.Viewer/TestComponents/Select/{MultiSelectWithInitialValues.razor => MultiSelectWithInitialValuesTest.razor} (94%)
create mode 100644 src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectFitContentTest.razor
diff --git a/src/MudBlazor.Docs/Pages/Components/Select/Examples/SelectFitContentExample.razor b/src/MudBlazor.Docs/Pages/Components/Select/Examples/SelectFitContentExample.razor
new file mode 100644
index 000000000000..5d599d4a4ddb
--- /dev/null
+++ b/src/MudBlazor.Docs/Pages/Components/Select/Examples/SelectFitContentExample.razor
@@ -0,0 +1,51 @@
+@namespace MudBlazor.Docs.Examples
+@using MudBlazor
+
+
+ @foreach (var state in _states)
+ {
+ @state
+ }
+
+
+
+ @foreach (var state in _states)
+ {
+ @state
+ }
+
+
+
+ @foreach (var state in _states)
+ {
+ @state
+ }
+
+
+
+
+
+
+
+@code {
+ private bool _fullWidth;
+ private bool _fitContent;
+ private string _selectedState;
+ private readonly string[] _states =
+ [
+ "Alabama", "Alaska", "American Samoa", "Arizona",
+ "Arkansas", "California", "Colorado", "Connecticut",
+ "Delaware", "District of Columbia", "Federated States of Micronesia",
+ "Florida", "Georgia", "Guam", "Hawaii", "Idaho",
+ "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky",
+ "Louisiana", "Maine", "Marshall Islands", "Maryland",
+ "Massachusetts", "Michigan", "Minnesota", "Mississippi",
+ "Missouri", "Montana", "Nebraska", "Nevada",
+ "New Hampshire", "New Jersey", "New Mexico", "New York",
+ "North Carolina", "North Dakota", "Northern Mariana Islands", "Ohio",
+ "Oklahoma", "Oregon", "Palau", "Pennsylvania", "Puerto Rico",
+ "Rhode Island", "South Carolina", "South Dakota", "Tennessee",
+ "Texas", "Utah", "Vermont", "Virgin Island", "Virginia",
+ "Washington", "West Virginia", "Wisconsin", "Wyoming"
+ ];
+}
diff --git a/src/MudBlazor.Docs/Pages/Components/Select/Examples/SelectPlaygroundExample.razor b/src/MudBlazor.Docs/Pages/Components/Select/Examples/SelectPlaygroundExample.razor
index 0ad810f62ec8..044cb79150db 100644
--- a/src/MudBlazor.Docs/Pages/Components/Select/Examples/SelectPlaygroundExample.razor
+++ b/src/MudBlazor.Docs/Pages/Components/Select/Examples/SelectPlaygroundExample.razor
@@ -10,6 +10,7 @@
Label="@variant.ToString()"
Margin="_margin"
Dense="_dense"
+ FitContent="_fitContent"
Disabled="_disabled"
ReadOnly="_readonly"
Placeholder="@(_placeholder ? "Placeholder" : null)"
@@ -39,21 +40,23 @@
+
@code {
- string _value;
- Margin _margin;
- bool _dense;
- bool _disabled;
- bool _readonly;
- bool _placeholder;
- bool _helperText;
- bool _helperTextOnFocus;
- bool _clearable;
+ private string _value;
+ private Margin _margin;
+ private bool _dense;
+ private bool _disabled;
+ private bool _readonly;
+ private bool _placeholder;
+ private bool _helperText;
+ private bool _helperTextOnFocus;
+ private bool _clearable;
+ private bool _fitContent;
- private string[] _states =
+ private readonly string[] _states =
{
"Alabama", "Alaska", "Arizona", "Arkansas", "California",
"Colorado", "Connecticut", "Delaware", "Florida", "Georgia",
diff --git a/src/MudBlazor.Docs/Pages/Components/Select/SelectPage.razor b/src/MudBlazor.Docs/Pages/Components/Select/SelectPage.razor
index aacb293b2ad2..d991bbc55808 100644
--- a/src/MudBlazor.Docs/Pages/Components/Select/SelectPage.razor
+++ b/src/MudBlazor.Docs/Pages/Components/Select/SelectPage.razor
@@ -77,6 +77,37 @@
+
+
+
+ The MudSelect component provides dynamic sizing capabilities through the FullWidth and FitContent parameters, allowing it to adapt to different layout needs:
+
+
+
+ Default Behavior - FullWidth=False : The component expands to fill the available space within its container, adjusting dynamically based on its surroundings.
+
+
+
+
+ Full-Width Mode - FullWidth=True : Forces the component to stretch and occupy 100% of its parent container’s width.
+
+
+
+
+ Fit-Content Mode - FitContent=True : Instead of expanding, the component adjusts its width to fit only the necessary space required for its content.
+
+
+
+
+ Note: When using FitContent, the width of the select component can depend on the Placeholder, Label, Adornment, and Clearable properties.
+
+
+
+
+
+
+
+
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectCustomizedTextTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectCustomizedTextTest.razor
index e3ce26d97317..c78ffb181e95 100644
--- a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectCustomizedTextTest.razor
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectCustomizedTextTest.razor
@@ -38,4 +38,4 @@
{
public required string A { get; init; }
}
-}
\ No newline at end of file
+}
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectTest2.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectTest2.razor
index 13e017cde297..b0f5a227baaf 100644
--- a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectTest2.razor
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectTest2.razor
@@ -26,4 +26,4 @@
base.OnInitialized();
}
-}
\ No newline at end of file
+}
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectTest5.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectTest5.razor
index 728b5598f907..b779b7d0ba87 100644
--- a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectTest5.razor
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectTest5.razor
@@ -1,4 +1,4 @@
-@if (_posts is not null)
+@if (_posts is not null)
{
@foreach (var role in _posts)
@@ -22,4 +22,4 @@
base.OnInitialized();
}
-}
\ No newline at end of file
+}
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectTest7.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectTest7.razor
index 74bcfb52df0c..19132ff75dda 100644
--- a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectTest7.razor
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectTest7.razor
@@ -33,4 +33,4 @@
base.OnInitialized();
}
-}
\ No newline at end of file
+}
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectTestRequiredValue.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectTestRequiredValue.razor
index ce786096b5e0..b230851b8d55 100644
--- a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectTestRequiredValue.razor
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectTestRequiredValue.razor
@@ -29,7 +29,6 @@
Value: @_objectSelect?.Value?.Name
Selected values: @((_objectSelect?.SelectedValues is null) ? null : string.Join(", ", _objectSelect.SelectedValues.Select(x => x!.Name)))
-
@code {
public static string __description__ = "Multi-Select Required Should Recognize Values";
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectWithInitialValues.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectWithInitialValuesTest.razor
similarity index 94%
rename from src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectWithInitialValues.razor
rename to src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectWithInitialValuesTest.razor
index 1d00cc0d7c58..73a0a08933d5 100644
--- a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectWithInitialValues.razor
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectWithInitialValuesTest.razor
@@ -12,7 +12,7 @@
@code {
- private readonly List _allItems = [];
+ private readonly List _allItems = [];
private readonly HashSet _selectedItems = [];
protected override async Task OnInitializedAsync()
@@ -32,4 +32,4 @@
{
public required string A { get; init; }
}
-}
\ No newline at end of file
+}
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectWithValueContainZeroTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectWithValueContainZeroTest.razor
index ff7b27dfae2d..a881606f81f8 100644
--- a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectWithValueContainZeroTest.razor
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectWithValueContainZeroTest.razor
@@ -40,4 +40,4 @@
Value2,
Value3
}
-}
\ No newline at end of file
+}
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/ReloadSelectItemsTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/ReloadSelectItemsTest.razor
index 69ff9c777b6a..9065e13659b2 100644
--- a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/ReloadSelectItemsTest.razor
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/ReloadSelectItemsTest.razor
@@ -18,7 +18,7 @@
@code {
public static string __description__ = "After clicking 'load new states' selecting items should result in the correct values being selected.";
-
+
private string _value = "Nothing selected";
private readonly List _states2 = ["Alabama", "Alaska", "American Samoa"];
private List _states =
@@ -44,4 +44,4 @@
_value = "Nothing selected";
_states = _states2;
}
-}
\ No newline at end of file
+}
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/ReselectValueTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/ReselectValueTest.razor
index 0d66375be005..ea3b0bd78dc9 100644
--- a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/ReselectValueTest.razor
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/ReselectValueTest.razor
@@ -16,4 +16,4 @@
{
ChangeCount++;
}
-}
\ No newline at end of file
+}
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectFitContentTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectFitContentTest.razor
new file mode 100644
index 000000000000..0ddd543b7166
--- /dev/null
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectFitContentTest.razor
@@ -0,0 +1,47 @@
+
+ @foreach (var state in _states)
+ {
+ @state
+ }
+
+
+
+ @foreach (var state in _states)
+ {
+ @state
+ }
+
+
+
+
+
+
+
+@code {
+ public static string __description__ = "FullWidth should override FitContent";
+
+ [Parameter]
+ public bool FullWidth { get; set; }
+
+ [Parameter]
+ public bool FitContent { get; set; }
+
+ private string? _selectedState;
+ private readonly string[] _states =
+ [
+ "Alabama", "Alaska", "American Samoa", "Arizona",
+ "Arkansas", "California", "Colorado", "Connecticut",
+ "Delaware", "District of Columbia", "Federated States of Micronesia",
+ "Florida", "Georgia", "Guam", "Hawaii", "Idaho",
+ "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky",
+ "Louisiana", "Maine", "Marshall Islands", "Maryland",
+ "Massachusetts", "Michigan", "Minnesota", "Mississippi",
+ "Missouri", "Montana", "Nebraska", "Nevada",
+ "New Hampshire", "New Jersey", "New Mexico", "New York",
+ "North Carolina", "North Dakota", "Northern Mariana Islands", "Ohio",
+ "Oklahoma", "Oregon", "Palau", "Pennsylvania", "Puerto Rico",
+ "Rhode Island", "South Carolina", "South Dakota", "Tennessee",
+ "Texas", "Utah", "Vermont", "Virgin Island", "Virginia",
+ "Washington", "West Virginia", "Wisconsin", "Wyoming"
+ ];
+}
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectFocusAndTypeTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectFocusAndTypeTest.razor
index 233f52bacecd..04d66ca27837 100644
--- a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectFocusAndTypeTest.razor
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectFocusAndTypeTest.razor
@@ -23,4 +23,4 @@
"Tennessee", "Texas", "Utah", "Vermont", "Virginia",
"Washington", "West Virginia", "Wisconsin", "Wyoming"
};
-}
\ No newline at end of file
+}
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectRequiredTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectRequiredTest.razor
index 396c05854b62..4b82d3f67c38 100644
--- a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectRequiredTest.razor
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectRequiredTest.razor
@@ -12,4 +12,4 @@
public static string __description__ = "The Select should not show required-error initially. The first (empty) item should.";
private string? _value;
-}
\ No newline at end of file
+}
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectScrollDrawerTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectScrollDrawerTest.razor
index d611002a8eb4..64188d106e19 100644
--- a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectScrollDrawerTest.razor
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectScrollDrawerTest.razor
@@ -6,7 +6,7 @@
My App
-
+
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectTest1.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectTest1.razor
index 5ec486b56f29..c111e4fc59d2 100644
--- a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectTest1.razor
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectTest1.razor
@@ -10,18 +10,18 @@
@code {
- public static string __description__ = "Click should open the Menu and selecting a value should update the bindable value.";
+ public static string __description__ = "Click should open the Menu and selecting a value should update the bindable value.";
- private string? _value = null;
- private bool _focused;
+ private string? _value = null;
+ private bool _focused;
- private void Focused()
- {
- _focused = true;
- }
+ private void Focused()
+ {
+ _focused = true;
+ }
- private void Blurred()
- {
- _focused = false;
- }
+ private void Blurred()
+ {
+ _focused = false;
+ }
}
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectValidationDataAttrTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectValidationDataAttrTest.razor
index 6c1174b7b8e8..c3576328023f 100644
--- a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectValidationDataAttrTest.razor
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectValidationDataAttrTest.razor
@@ -15,6 +15,7 @@
+
@code {
public static string __description__ = "Test use of data attributes accessible using `For` bound expression.";
@@ -28,4 +29,4 @@
[StringLength(3, ErrorMessage = "Should not be longer than 3")]
public string? Value { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectVariantsTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectVariantsTest.razor
index faf42ba3b6ab..fc0d841256cc 100644
--- a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectVariantsTest.razor
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectVariantsTest.razor
@@ -27,4 +27,4 @@
@code {
public static string __description__ = "The first two selects are Strict and should not crash in WASM on navigating the list with arrows.";
-}
\ No newline at end of file
+}
diff --git a/src/MudBlazor.UnitTests/Components/SelectTests.cs b/src/MudBlazor.UnitTests/Components/SelectTests.cs
index a0a16fd4038d..a3f99c3c9d05 100644
--- a/src/MudBlazor.UnitTests/Components/SelectTests.cs
+++ b/src/MudBlazor.UnitTests/Components/SelectTests.cs
@@ -674,7 +674,7 @@ public void SingleSelect_Should_CallValidationFunc()
[Test]
public void MultiSelect_Initial_Values()
{
- var comp = Context.RenderComponent();
+ var comp = Context.RenderComponent();
// print the generated html
// select the input of the select
@@ -1524,7 +1524,7 @@ public void ReadOnlyShouldNotHaveClearButton()
}
[Test]
- public async Task SelectFullWidthTest()
+ public async Task SelectPopoverFullWidthTest()
{
var comp = Context.RenderComponent();
@@ -1548,6 +1548,41 @@ public async Task SelectFullWidthTest()
comp.Find(".expanded").ClassList.Should().Contain("mud-popover-open").And.NotContain("mud-popover-relative-width");
}
+ [Test]
+ public void SelectFitContentTest()
+ {
+ var comp = Context.RenderComponent();
+
+ //default values
+ comp.Instance.FullWidth.Should().BeFalse();
+ comp.Instance.FitContent.Should().BeFalse();
+
+ var select = comp.Find(".mud-select");
+
+ select.ClassList.Should().NotContain("mud-width-content");
+
+ //set fit content
+ comp.SetParametersAndRender(parameters => parameters.Add(c => c.FitContent, true));
+
+ comp.Instance.FullWidth.Should().BeFalse();
+ comp.Instance.FitContent.Should().BeTrue();
+
+ select.ClassList.Should().Contain("mud-width-content");
+
+ var filler = comp.Find(".mud-select-filler");
+
+ filler.ClassList.Should().Contain("d-inline-block").And.Contain("mx-4");
+ filler.TextContent.Trim().Should().Be("Federated States of Micronesia");
+
+ //set full width
+ comp.SetParametersAndRender(parameters => parameters.Add(c => c.FullWidth, true));
+
+ comp.Instance.FullWidth.Should().BeTrue();
+ comp.Instance.FitContent.Should().BeTrue();
+
+ select.ClassList.Should().NotContain("mud-width-content");
+ }
+
[TestCaseSource(typeof(MouseEventArgsTestCase), nameof(MouseEventArgsTestCase.AllCombinations))]
[Test]
public async Task Select_HandleMouseDown(MouseEventArgs args)
diff --git a/src/MudBlazor/Components/Select/MudSelect.razor b/src/MudBlazor/Components/Select/MudSelect.razor
index 018a0573b68d..5d6e828b6b27 100644
--- a/src/MudBlazor/Components/Select/MudSelect.razor
+++ b/src/MudBlazor/Components/Select/MudSelect.razor
@@ -57,6 +57,39 @@
@GetSelectedValuePresenter()
}
+ @if (FitContent)
+ {
+
+ @{
+ var value = _longestItem is { Value: not null } ? ToStringFunc ?.Invoke(_longestItem.Value) ?? Converter.Set(_longestItem.Value) : string.Empty;
+ var useLabel = Label is not null && Placeholder is null && !ShrinkLabel
+ && (Adornment != Adornment.Start || (AdornmentIcon is null && AdornmentText is null));
+
+ var defaultValue = useLabel ? Label : Placeholder;
+ }
+ @if (defaultValue?.Length > value!.Length)
+ {
+ @(MultiSelectionTextFunc?.Invoke([defaultValue]) ?? defaultValue)
+ }
+ else if (_longestItem?.ChildContent is null || MultiSelectionTextFunc is not null)
+ {
+ @(MultiSelectionTextFunc?.Invoke([value!]) ?? value!)
+ }
+ else
+ {
+ @_longestItem.ChildContent
+ }
+ @AdornmentText
+ @if (AdornmentText is null)
+ {
+
+ }
+ @if (Clearable)
+ {
+
+ }
+
+ }
-
CloseMenu(false))" LockScroll="@LockScroll" />
diff --git a/src/MudBlazor/Components/Select/MudSelect.razor.cs b/src/MudBlazor/Components/Select/MudSelect.razor.cs
index 864bbc97ca76..5ccc0430a147 100644
--- a/src/MudBlazor/Components/Select/MudSelect.razor.cs
+++ b/src/MudBlazor/Components/Select/MudSelect.razor.cs
@@ -23,6 +23,8 @@ public partial class MudSelect : MudBaseInput, IMudSelect, IMudShadowSelec
private string? _activeItemId;
private bool? _selectAllChecked;
private string? _multiSelectionText;
+ private int _longestItemLength;
+ private MudSelectItem? _longestItem;
private IEqualityComparer? _comparer;
private TaskCompletionSource? _renderComplete;
private MudInput _elementReference = null!;
@@ -36,6 +38,7 @@ public partial class MudSelect : MudBaseInput, IMudSelect, IMudShadowSelec
protected string OuterClassname =>
new CssBuilder("mud-select")
.AddClass("mud-width-full", FullWidth)
+ .AddClass("mud-width-content", FitContent && !FullWidth)
.AddClass(OuterClass)
.Build();
@@ -49,6 +52,14 @@ public partial class MudSelect : MudBaseInput, IMudSelect, IMudShadowSelec
.AddClass(InputClass)
.Build();
+ protected string FillerClassname =>
+ new CssBuilder("mud-select-filler")
+ .AddClass("d-inline-block")
+ .AddClass("invisible")
+ .AddClass("mx-2", Variant == Variant.Text)
+ .AddClass("mx-4", Variant != Variant.Text)
+ .Build();
+
[Inject]
private IKeyInterceptorService KeyInterceptorService { get; set; } = null!;
@@ -224,6 +235,16 @@ private async Task SelectLastItem()
[Category(CategoryTypes.Popover.Appearance)]
public DropdownWidth RelativeWidth { get; set; } = DropdownWidth.Relative;
+ ///
+ /// Sets the container width to match its contents.
+ ///
+ ///
+ /// Defaults to false . Requires FullWidth to be false
+ ///
+ [Parameter]
+ [Category(CategoryTypes.FormComponent.Appearance)]
+ public bool FitContent { get; set; }
+
///
/// The CSS classes applied to the outer div .
///
@@ -1283,7 +1304,20 @@ public void RegisterShadowItem(MudSelectItem? item)
{
if (item == null || item.Value == null)
return;
+
_shadowLookup[item.Value] = item;
+
+ if (!FitContent) return;
+
+ var stringValue = ToStringFunc?.Invoke(item.Value) ?? Converter.Set(item.Value);
+
+ if (_longestItem is null || stringValue?.Length > _longestItemLength)
+ {
+ _longestItem = item;
+ _longestItemLength = stringValue?.Length ?? 0;
+
+ StateHasChanged();
+ }
}
///
diff --git a/src/MudBlazor/Styles/components/_select.scss b/src/MudBlazor/Styles/components/_select.scss
index 9cb64bebc7eb..cd5f8b064281 100644
--- a/src/MudBlazor/Styles/components/_select.scss
+++ b/src/MudBlazor/Styles/components/_select.scss
@@ -97,3 +97,12 @@
border-bottom: 1px solid lightgray;
padding-bottom: 18px;
}
+
+.mud-select-filler {
+ white-space: nowrap;
+ height: 0px;
+}
+
+.mud-width-content {
+ max-width: min-content;
+}
From 0d7c5cdf575fc303108d806d9904fe4a19b32720 Mon Sep 17 00:00:00 2001
From: Versile Johnson II <148913404+versile2@users.noreply.github.com>
Date: Sat, 22 Mar 2025 12:04:59 -0500
Subject: [PATCH 12/22] MudToolTip: Move Duration Delay (#11051)
---
.../Tooltip/TooltipDurationDelayTest.razor | 17 ++++
.../Tooltip/TooltipInMenuTest.razor | 20 +++++
.../Components/ToolTipTests.cs | 78 +++++++++++++++++++
.../TestData/MouseEventArgsTestCase.cs | 17 ++--
.../Components/Tooltip/MudTooltip.razor | 2 +-
.../Components/Tooltip/MudTooltip.razor.cs | 49 ++++++++++--
.../Utilities/Debounce/DebounceDispatcher.cs | 22 ++++--
7 files changed, 181 insertions(+), 24 deletions(-)
create mode 100644 src/MudBlazor.UnitTests.Viewer/TestComponents/Tooltip/TooltipDurationDelayTest.razor
create mode 100644 src/MudBlazor.UnitTests.Viewer/TestComponents/Tooltip/TooltipInMenuTest.razor
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Tooltip/TooltipDurationDelayTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Tooltip/TooltipDurationDelayTest.razor
new file mode 100644
index 000000000000..34a60bbacd0e
--- /dev/null
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Tooltip/TooltipDurationDelayTest.razor
@@ -0,0 +1,17 @@
+
+ Button With Tooltip
+
+
+@code {
+ public static string __description__ = "Used to test Tooltip Duration and Delay";
+
+ [Parameter]
+ public double Duration { get; set; } = 1000;
+
+ [Parameter]
+ public double Delay { get; set; } = 500;
+
+ [Parameter]
+ public bool ShowOnHover { get; set; } = true;
+}
+
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Tooltip/TooltipInMenuTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Tooltip/TooltipInMenuTest.razor
new file mode 100644
index 000000000000..48eaf94298c7
--- /dev/null
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Tooltip/TooltipInMenuTest.razor
@@ -0,0 +1,20 @@
+MudTooltip is @Text
+
+
+
+ Some button with flashing tooltip
+
+
+
+
+
+
+ Some button with normal tooltip
+
+
+
+@code {
+ public static string __description__ = "Shows tooltips in a menu";
+
+ private const string Text = "Testing";
+}
diff --git a/src/MudBlazor.UnitTests/Components/ToolTipTests.cs b/src/MudBlazor.UnitTests/Components/ToolTipTests.cs
index 86c49681a601..fb112497bba3 100644
--- a/src/MudBlazor.UnitTests/Components/ToolTipTests.cs
+++ b/src/MudBlazor.UnitTests/Components/ToolTipTests.cs
@@ -356,5 +356,83 @@ public async Task Tooltip_Disabled_Button_OnPointerEnter_NoPopover()
await button.ParentElement.TriggerEventAsync("onpointerenter", new PointerEventArgs());
comp.FindAll("div.mud-popover-open").Count.Should().Be(0);
}
+
+ [TestCase(0, 0)]
+ [TestCase(500, 500)]
+ [Test]
+ public void Tooltip_Debouncer_Initials(double duration, double delay)
+ {
+ var comp = Context.RenderComponent(p =>
+ {
+ p.Add(x => x.Delay, delay);
+ p.Add(x => x.Duration, duration);
+ });
+
+ var tooltipComp = comp.FindComponent().Instance;
+ tooltipComp.Delay.Should().Be(delay);
+ tooltipComp.Duration.Should().Be(duration);
+ tooltipComp._previousDelay.Should().Be(delay);
+ tooltipComp._previousDuration.Should().Be(duration);
+ var button = comp.Find("button");
+ button.Should().NotBeNull();
+ }
+
+ [TestCase(0, 0)]
+ [TestCase(500, 500)]
+ [Test]
+ public async Task Tooltip_Debouncer_Duration_and_Delay(double duration, double delay)
+ {
+ var comp = Context.RenderComponent(p =>
+ {
+ p.Add(x => x.Delay, delay);
+ p.Add(x => x.Duration, duration);
+ });
+
+ var tooltipComp = comp.FindComponent().Instance;
+
+ // cannot await or it waits until the debounce happens
+ var eventTask = tooltipComp.HandlePointerEnterAsync();
+ if (delay > 0)
+ tooltipComp.ShowToolTip().Should().BeFalse();
+
+ await Task.Delay((int)delay + 50);
+ tooltipComp.ShowToolTip().Should().BeTrue();
+
+ await eventTask; // ensure all completed
+
+ // cannot await or it waits until the debounce happens
+ eventTask = tooltipComp.HandlePointerLeaveAsync();
+ if (duration > 0)
+ tooltipComp.ShowToolTip().Should().BeTrue();
+
+ await Task.Delay((int)(duration + delay) + 50);
+ tooltipComp.ShowToolTip().Should().BeFalse();
+
+ await eventTask;
+ }
+
+ [TestCase(true)]
+ [TestCase(false)]
+ [Test]
+ public async Task Tooltip_ShowOnHover(bool showOnHover)
+ {
+ var comp = Context.RenderComponent(p =>
+ {
+ p.Add(x => x.ShowOnHover, showOnHover);
+ });
+ // we don't need to await Task.Delay to account for Delay/Duration since the await Handle takes care of it.
+ var tooltipComp = comp.FindComponent().Instance;
+ tooltipComp.ShowOnHover.Should().Be(showOnHover);
+ if (!showOnHover)
+ {
+ await tooltipComp.HandlePointerEnterAsync();
+ tooltipComp.ShowToolTip().Should().BeFalse();
+ }
+ else
+ {
+ await tooltipComp.HandlePointerEnterAsync();
+ tooltipComp.ShowToolTip().Should().BeTrue();
+ }
+ }
}
}
diff --git a/src/MudBlazor.UnitTests/TestData/MouseEventArgsTestCase.cs b/src/MudBlazor.UnitTests/TestData/MouseEventArgsTestCase.cs
index 9a9395878570..a8cb6c97d5bf 100644
--- a/src/MudBlazor.UnitTests/TestData/MouseEventArgsTestCase.cs
+++ b/src/MudBlazor.UnitTests/TestData/MouseEventArgsTestCase.cs
@@ -1,9 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Components.Web;
+using Microsoft.AspNetCore.Components.Web;
using NUnit.Framework;
namespace MudBlazor.UnitTests.TestData
@@ -13,11 +8,11 @@ public static class MouseEventArgsTestCase
public static TestCaseData[] AllCombinations()
{
return
- [
- new TestCaseData(new MouseEventArgs { Button = 0}),
- new TestCaseData (new MouseEventArgs { Button = 1 }),
- new TestCaseData (new MouseEventArgs { Button = 2 }),
- ];
+ [
+ new TestCaseData(new MouseEventArgs { Button = 0 }),
+ new TestCaseData(new MouseEventArgs { Button = 1 }),
+ new TestCaseData(new MouseEventArgs { Button = 2 }),
+ ];
}
}
}
diff --git a/src/MudBlazor/Components/Tooltip/MudTooltip.razor b/src/MudBlazor/Components/Tooltip/MudTooltip.razor
index b51243294822..3a9f2a6a2449 100644
--- a/src/MudBlazor/Components/Tooltip/MudTooltip.razor
+++ b/src/MudBlazor/Components/Tooltip/MudTooltip.razor
@@ -5,7 +5,7 @@
@ChildContent
@if (ShowToolTip())
{
-
+
@if (TooltipContent is not null)
{
diff --git a/src/MudBlazor/Components/Tooltip/MudTooltip.razor.cs b/src/MudBlazor/Components/Tooltip/MudTooltip.razor.cs
index 4138b507013b..56f438cee950 100644
--- a/src/MudBlazor/Components/Tooltip/MudTooltip.razor.cs
+++ b/src/MudBlazor/Components/Tooltip/MudTooltip.razor.cs
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Components;
using MudBlazor.State;
using MudBlazor.Utilities;
+using MudBlazor.Utilities.Debounce;
namespace MudBlazor
{
@@ -10,9 +11,16 @@ public partial class MudTooltip : MudComponentBase
private readonly ParameterState
_visibleState;
private Origin _anchorOrigin;
private Origin _transformOrigin;
-
+ internal DebounceDispatcher _showDebouncer;
+ internal DebounceDispatcher _hideDebouncer;
+ internal double _previousDelay;
+ internal double _previousDuration;
public MudTooltip()
{
+ _previousDelay = Delay;
+ _showDebouncer = new DebounceDispatcher(TimeSpan.FromMilliseconds(Delay));
+ _previousDuration = Duration;
+ _hideDebouncer = new DebounceDispatcher(TimeSpan.FromMilliseconds(Duration));
using var registerScope = CreateRegisterScope();
_visibleState = registerScope.RegisterParameter(nameof(Visible))
.WithParameter(() => Visible)
@@ -162,21 +170,50 @@ public MudTooltip()
///
/// Register and Show the Popover for the tooltip if it is not disabled, set to be visible, the content or Text is not empty or null
///
- private bool ShowToolTip()
+ internal bool ShowToolTip()
{
if (_anchorOrigin == Origin.TopLeft || _transformOrigin == Origin.TopLeft)
ConvertPlacement();
return !Disabled && _visibleState.Value && (TooltipContent is not null || !string.IsNullOrEmpty(Text));
}
- private Task HandlePointerEnterAsync()
+ protected override void OnParametersSet()
+ {
+ base.OnParametersSet();
+
+ if (Math.Abs(_previousDelay - Delay) > .001)
+ {
+ _showDebouncer = new DebounceDispatcher(TimeSpan.FromMilliseconds(Delay));
+ _previousDelay = Delay;
+ }
+
+ if (Math.Abs(_previousDuration - Duration) > .001)
+ {
+ _hideDebouncer = new DebounceDispatcher(TimeSpan.FromMilliseconds(Duration));
+ _previousDuration = Duration;
+ }
+ }
+
+ internal Task HandlePointerEnterAsync()
{
- return ShowOnHover ? _visibleState.SetValueAsync(true) : Task.CompletedTask;
+ if (!ShowOnHover)
+ {
+ return Task.CompletedTask;
+ }
+
+ _hideDebouncer.Cancel();
+ return _showDebouncer.DebounceAsync(() => _visibleState.SetValueAsync(true));
}
- private Task HandlePointerLeaveAsync()
+ internal Task HandlePointerLeaveAsync()
{
- return ShowOnHover ? _visibleState.SetValueAsync(false) : Task.CompletedTask;
+ if (!ShowOnHover)
+ {
+ return Task.CompletedTask;
+ }
+
+ _showDebouncer.Cancel();
+ return _hideDebouncer.DebounceAsync(() => _visibleState.SetValueAsync(false));
}
private Task HandleFocusInAsync()
diff --git a/src/MudBlazor/Utilities/Debounce/DebounceDispatcher.cs b/src/MudBlazor/Utilities/Debounce/DebounceDispatcher.cs
index 7ca3b97cefe1..827bda1c8fd5 100644
--- a/src/MudBlazor/Utilities/Debounce/DebounceDispatcher.cs
+++ b/src/MudBlazor/Utilities/Debounce/DebounceDispatcher.cs
@@ -2,10 +2,6 @@
// MudBlazor licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-
namespace MudBlazor.Utilities.Debounce;
#nullable enable
@@ -15,7 +11,7 @@ namespace MudBlazor.Utilities.Debounce;
///
internal class DebounceDispatcher
{
- private readonly int _interval;
+ private readonly TimeSpan _interval;
private CancellationTokenSource? _cancellationTokenSource;
///
@@ -23,6 +19,15 @@ internal class DebounceDispatcher
///
/// The minimum interval in milliseconds between invocations of the debounced function.
public DebounceDispatcher(int interval)
+ : this(TimeSpan.FromMilliseconds(interval))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with the specified interval.
+ ///
+ /// The minimum interval as a between invocations of the debounced function.
+ public DebounceDispatcher(TimeSpan interval)
{
_interval = interval;
}
@@ -39,7 +44,7 @@ public DebounceDispatcher(int interval)
/// A Task representing the asynchronous operation with minimal delay.
public async Task DebounceAsync(Func action, CancellationToken cancellationToken = default)
{
- // ReSharper disable MethodHasAsyncOverload (not available in .net7)
+ // ReSharper disable MethodHasAsyncOverload (we should not use it as it behaves differently)
// Cancel the previous debounce task if it exists
_cancellationTokenSource?.Cancel();
// ReSharper restore MethodHasAsyncOverload
@@ -59,4 +64,9 @@ public async Task DebounceAsync(Func action, CancellationToken cancellatio
// If the task was canceled, ignore it
}
}
+
+ ///
+ /// Cancel the current debounced task.
+ ///
+ public void Cancel() => _cancellationTokenSource?.Cancel();
}
From 84ccc60dbb7873b65bde333da315bf1822644f49 Mon Sep 17 00:00:00 2001
From: Versile Johnson II <148913404+versile2@users.noreply.github.com>
Date: Wed, 26 Mar 2025 10:53:59 -0500
Subject: [PATCH 13/22] MudInput: Dispose JS OnBlur (#11039)
---
src/MudBlazor.UnitTests/Components/TextFieldTests.cs | 2 +-
src/MudBlazor/Components/Input/MudInput.razor.cs | 9 ++++++---
.../Extensions/ElementReferenceExtensions.cs | 9 ++++++---
src/MudBlazor/TScripts/mudElementReference.js | 11 +++++++++--
4 files changed, 22 insertions(+), 9 deletions(-)
diff --git a/src/MudBlazor.UnitTests/Components/TextFieldTests.cs b/src/MudBlazor.UnitTests/Components/TextFieldTests.cs
index c8cf92ada31b..e3100de88b80 100644
--- a/src/MudBlazor.UnitTests/Components/TextFieldTests.cs
+++ b/src/MudBlazor.UnitTests/Components/TextFieldTests.cs
@@ -1042,7 +1042,7 @@ public async Task DebouncedTextFieldRerenderTest()
// trigger first value change
await Task.Delay(comp.Instance.DebounceInterval);
// trigger delayed re-render
- comp.Find("#re-render-button").Click();
+ await comp.InvokeAsync(() => comp.Find("#re-render-button").Click());
// imitate "typing in progress" by extending the debounce interval until component re-renders
var elapsedTime = 0;
var currentText = "test";
diff --git a/src/MudBlazor/Components/Input/MudInput.razor.cs b/src/MudBlazor/Components/Input/MudInput.razor.cs
index f5dbad93b19c..5080d00a954f 100644
--- a/src/MudBlazor/Components/Input/MudInput.razor.cs
+++ b/src/MudBlazor/Components/Input/MudInput.razor.cs
@@ -373,9 +373,13 @@ or InputType.Time
///
protected override async ValueTask DisposeAsyncCore()
{
- if (AutoGrow && IsJSRuntimeAvailable)
+ if (IsJSRuntimeAvailable)
{
- await JsRuntime.InvokeVoidAsyncWithErrorHandling("mudInputAutoGrow.destroy", ElementReference);
+ await JsRuntime.InvokeVoidAsyncWithErrorHandling("mudElementRef.removeOnBlurEvent", ElementReference, _dotNetReferenceLazy);
+ if (AutoGrow)
+ {
+ await JsRuntime.InvokeVoidAsyncWithErrorHandling("mudInputAutoGrow.destroy", ElementReference);
+ }
}
await base.DisposeAsyncCore();
@@ -388,7 +392,6 @@ public async Task CallOnBlurredAsync()
if (!_isFocused)
return;
- await InvokeAsync(StateHasChanged);
await OnBlurredAsync(new FocusEventArgs { Type = "jsBlur.OnBlur" });
}
}
diff --git a/src/MudBlazor/Extensions/ElementReferenceExtensions.cs b/src/MudBlazor/Extensions/ElementReferenceExtensions.cs
index ecffbbfc9d49..0327ca85b78f 100644
--- a/src/MudBlazor/Extensions/ElementReferenceExtensions.cs
+++ b/src/MudBlazor/Extensions/ElementReferenceExtensions.cs
@@ -1,7 +1,5 @@
-using System;
-using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
-using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using MudBlazor.Interop;
@@ -71,5 +69,10 @@ public static ValueTask RemoveDefaultPreventingHandlers(this ElementReference el
this ElementReference elementReference,
DotNetObjectReference obj) where T : class =>
elementReference.GetJSRuntime()?.InvokeVoidAsync("mudElementRef.addOnBlurEvent", elementReference, obj) ?? ValueTask.CompletedTask;
+
+ public static ValueTask MudDetachBlurEventWithJS<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>(
+ this ElementReference elementReference,
+ DotNetObjectReference obj) where T : class =>
+ elementReference.GetJSRuntime()?.InvokeVoidAsync("mudElementRef.removeOnBlurEvent", elementReference, obj) ?? ValueTask.CompletedTask;
}
}
diff --git a/src/MudBlazor/TScripts/mudElementReference.js b/src/MudBlazor/TScripts/mudElementReference.js
index 478b116321b3..a810cde4813e 100644
--- a/src/MudBlazor/TScripts/mudElementReference.js
+++ b/src/MudBlazor/TScripts/mudElementReference.js
@@ -150,7 +150,8 @@ class MudElementReference {
// ios doesn't trigger Blazor/React/Other dom style blur event so add a base event listener here
// that will trigger with IOS Done button and regular blur events
addOnBlurEvent(element, dotNetReference) {
- function onFocusOut(e) {
+ element._mudBlurHandler = function (e) {
+ if (!element) return;
e.preventDefault();
element.blur();
if (dotNetReference) {
@@ -160,7 +161,13 @@ class MudElementReference {
console.error("No dotNetReference found for iosKeyboardFocus");
}
}
- element.addEventListener('blur', onFocusOut);
+ element.addEventListener('blur', element._mudBlurHandler);
+ }
+ // dispose event
+ removeOnBlurEvent(element, dotnetRef) {
+ if (!element || !element._mudBlurHandler) return;
+ element.removeEventListener('blur', element._mudBlurHandler);
+ delete element._mudBlurHandler;
}
};
window.mudElementRef = new MudElementReference();
From b7abb69a864e645b1605368ff4a4bbaa10f1cd11 Mon Sep 17 00:00:00 2001
From: Lukas Merz <81758471+LukasMerz@users.noreply.github.com>
Date: Thu, 27 Mar 2025 14:17:30 +0100
Subject: [PATCH 14/22] MudDatePicker: Fix selecting DateTime.MaxValue (#7285)
(#7286)
Co-authored-by: Artyom M.
Co-authored-by: Anu6is
---
.../DatePicker/SimpleMudDatePickerTest.razor | 1 +
.../Components/DatePickerTests.cs | 14 ++++++++++++++
.../DatePicker/MudBaseDatePicker.razor | 4 +++-
.../DatePicker/MudBaseDatePicker.razor.cs | 19 ++++++++++++++++---
4 files changed, 34 insertions(+), 4 deletions(-)
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/DatePicker/SimpleMudDatePickerTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/DatePicker/SimpleMudDatePickerTest.razor
index 4303fb8840b3..53038742b4f2 100644
--- a/src/MudBlazor.UnitTests.Viewer/TestComponents/DatePicker/SimpleMudDatePickerTest.razor
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/DatePicker/SimpleMudDatePickerTest.razor
@@ -70,6 +70,7 @@ else
[Parameter]
public Func? AdditionalDateClassesFunc { get; set; }
+ [Parameter]
public DateTime? Date { get; set; }
private async Task HandleDateChanged(DateTime? input)
diff --git a/src/MudBlazor.UnitTests/Components/DatePickerTests.cs b/src/MudBlazor.UnitTests/Components/DatePickerTests.cs
index 983ee7282f52..105a83b123dc 100644
--- a/src/MudBlazor.UnitTests/Components/DatePickerTests.cs
+++ b/src/MudBlazor.UnitTests/Components/DatePickerTests.cs
@@ -284,6 +284,20 @@ public void Check_Initial_Date_Format()
picker.Text.Should().Be("13/01/2021");
}
+ [Test]
+ public void Check_DateTime_MaxValue()
+ {
+ DateTime? date = DateTime.MaxValue;
+
+ var comp = OpenPicker(Parameter("Date", date));
+
+ comp.Instance.Date.Should().Be(DateTime.MaxValue);
+
+ comp.FindAll("button.mud-picker-calendar-day").First(x => x.TrimmedText().Equals("31")).ToMarkup().Should().Contain("mud-selected");
+ comp.Find("button.mud-button-date .mud-button-label").InnerHtml.Should().Be("Fri, 31 Dec");
+ comp.Find("button.mud-button-year .mud-button-label").InnerHtml.Should().Be("9999");
+ }
+
private IRenderedComponent OpenPicker(ComponentParameter parameter)
{
return OpenPicker(new[] { parameter });
diff --git a/src/MudBlazor/Components/DatePicker/MudBaseDatePicker.razor b/src/MudBlazor/Components/DatePicker/MudBaseDatePicker.razor
index 465232ff0da1..75787bb7349d 100644
--- a/src/MudBlazor/Components/DatePicker/MudBaseDatePicker.razor
+++ b/src/MudBlazor/Components/DatePicker/MudBaseDatePicker.razor
@@ -113,11 +113,12 @@
@GetWeekNumber(tempMonth, tempWeek)
}
+ var wasMaxValue = false;
@foreach (var day in GetWeek(tempMonth, tempWeek))
{
var tempId = ++dayId;
- @if (tempId != 0 || !firstMonthFirstYear)
+ @if ((tempId != 0 || !firstMonthFirstYear) && !wasMaxValue)
{
var selectedDay = !firstMonthFirstYear ? day : day.AddDays(-1);
}
+ wasMaxValue = day == Culture.Calendar.MaxSupportedDateTime;
}
}
diff --git a/src/MudBlazor/Components/DatePicker/MudBaseDatePicker.razor.cs b/src/MudBlazor/Components/DatePicker/MudBaseDatePicker.razor.cs
index 06cb4f7e1163..f454afbe38f6 100644
--- a/src/MudBlazor/Components/DatePicker/MudBaseDatePicker.razor.cs
+++ b/src/MudBlazor/Components/DatePicker/MudBaseDatePicker.razor.cs
@@ -342,6 +342,11 @@ protected DateTime GetMonthStart(int month)
{
return Culture.Calendar.MinSupportedDateTime;
}
+
+ if (_picker_month.HasValue && _picker_month.Value.Year == 9999 && _picker_month.Value.Month == 12 && month >= 1)
+ {
+ return Culture.Calendar.MaxSupportedDateTime;
+ }
return Culture.Calendar.AddMonths(monthStartDate, month);
}
@@ -371,9 +376,17 @@ protected IEnumerable GetWeek(int month, int index)
if (index is < 0 or > 5)
throw new ArgumentException("Index must be between 0 and 5");
var month_first = GetMonthStart(month);
- var week_first = month_first.AddDays(index * 7).StartOfWeek(GetFirstDayOfWeek());
- for (var i = 0; i < 7; i++)
- yield return week_first.AddDays(i);
+ if ((Culture.Calendar.MaxSupportedDateTime - month_first).Days >= index * 7)
+ {
+ var week_first = month_first.AddDays(index * 7).StartOfWeek(GetFirstDayOfWeek());
+ for (var i = 0; i < 7; i++)
+ {
+ if ((Culture.Calendar.MaxSupportedDateTime - week_first).Days >= i)
+ yield return week_first.AddDays(i);
+ else
+ yield return Culture.Calendar.MaxSupportedDateTime;
+ }
+ }
}
private string GetWeekNumber(int month, int index)
From 984a6d5d773fff9770b1495ce7480604be0445fe Mon Sep 17 00:00:00 2001
From: Versile Johnson II <148913404+versile2@users.noreply.github.com>
Date: Fri, 28 Mar 2025 14:00:48 -0500
Subject: [PATCH 15/22] MudPopover: Fix positioning delay (partial revert of
#10856) (#11107)
---
src/MudBlazor/TScripts/mudPopover.js | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/src/MudBlazor/TScripts/mudPopover.js b/src/MudBlazor/TScripts/mudPopover.js
index 8181b1a69407..318c46e43e0c 100644
--- a/src/MudBlazor/TScripts/mudPopover.js
+++ b/src/MudBlazor/TScripts/mudPopover.js
@@ -639,11 +639,10 @@ class MudPopover {
observer.observe(popoverContentNode, config);
- // Optimize resize observer
- const throttledResize = window.mudpopoverHelper.rafThrottle(entries => {
+ const resizeObserver = new ResizeObserver(entries => {
for (let entry of entries) {
const target = entry.target;
- for (let childNode of target.childNodes) {
+ for (const childNode of target.childNodes) {
if (childNode.id && childNode.id.startsWith('popover-')) {
window.mudpopoverHelper.placePopover(childNode);
}
@@ -651,16 +650,16 @@ class MudPopover {
}
});
- const resizeObserver = new ResizeObserver(throttledResize);
resizeObserver.observe(popoverNode.parentNode);
- const throttledContent = window.mudpopoverHelper.rafThrottle(entries => {
+ const contentNodeObserver = new ResizeObserver(entries => {
for (let entry of entries) {
- window.mudpopoverHelper.placePopoverByNode(entry.target);
+ const target = entry.target;
+ if (target)
+ window.mudpopoverHelper.placePopoverByNode(target);
}
});
- const contentNodeObserver = new ResizeObserver(throttledContent);
contentNodeObserver.observe(popoverContentNode);
this.map[id] = {
From 0834a037cb0f23f3239acb6223e90c61588cf78a Mon Sep 17 00:00:00 2001
From: Mark Radcliffe
Date: Sun, 30 Mar 2025 11:56:59 +1300
Subject: [PATCH 16/22] MudChart: Fix culture and perfomance issues (#11041)
Co-authored-by: ScarletKuro
---
.../Charts/Examples/BarExample1.razor | 12 +-
.../Examples/DonutCustomGraphicsExample.razor | 6 +-
.../Charts/Examples/DonutExample1.razor | 2 +-
.../Charts/Examples/LineExample1.razor | 18 +-
.../Charts/Examples/PieExample1.razor | 4 +-
.../Charts/Examples/StackedBarExample1.razor | 12 +-
.../Charts/Examples/TimeSeriesExample1.razor | 24 +-
.../Components/Charts/BarChartTests.cs | 26 +-
.../Charts/ChartSeriesExtensions.cs | 29 +++
.../Components/Charts/ChartToolTipTests.cs | 36 +++
.../Components/Charts/DonutChartTests.cs | 12 +
.../Components/Charts/LineChartTests.cs | 30 ++-
.../Components/Charts/PieChartTests.cs | 17 +-
.../Components/Charts/StackedBarChartTests.cs | 4 +-
.../Components/Charts/TimeSeriesChartTests.cs | 14 +-
.../Components/Chart/Charts/Bar.razor | 16 +-
.../Components/Chart/Charts/Bar.razor.cs | 45 ++--
.../Components/Chart/Charts/Donut.razor | 1 -
.../Components/Chart/Charts/Line.razor | 12 +-
.../Components/Chart/Charts/Line.razor.cs | 244 +++++++++---------
.../Components/Chart/Charts/Pie.razor | 18 +-
.../Components/Chart/Charts/Pie.razor.cs | 63 ++++-
.../Components/Chart/Charts/StackedBar.razor | 17 +-
.../Chart/Charts/StackedBar.razor.cs | 37 ++-
.../Components/Chart/Charts/TimeSeries.razor | 16 +-
.../Chart/Charts/TimeSeries.razor.cs | 216 +++++++++-------
.../Chart/Models/AxisChartOptions.cs | 11 +-
.../Components/Chart/Models/ChartOptions.cs | 2 +-
.../Components/Chart/Models/ChartSeries.cs | 5 +
.../Chart/Models/TimeSeriesChartSeries.cs | 5 +
.../Chart/MudCategoryAxisChartBase.cs | 71 ++++-
.../Components/Chart/MudChartBase.cs | 9 -
.../Components/Chart/Parts/ChartTooltip.razor | 12 +-
.../Chart/Parts/ChartTooltip.razor.cs | 5 +-
src/MudBlazor/GlobalUsings.cs | 1 +
src/MudBlazor/Interop/ElementSize.cs | 5 +
src/MudBlazor/TScripts/mudHelpers.js | 14 +-
src/MudBlazor/Utilities/StringHelpers.cs | 20 ++
38 files changed, 666 insertions(+), 425 deletions(-)
create mode 100644 src/MudBlazor.UnitTests/Components/Charts/ChartSeriesExtensions.cs
create mode 100644 src/MudBlazor.UnitTests/Components/Charts/ChartToolTipTests.cs
create mode 100644 src/MudBlazor/GlobalUsings.cs
create mode 100644 src/MudBlazor/Utilities/StringHelpers.cs
diff --git a/src/MudBlazor.Docs/Pages/Components/Charts/Examples/BarExample1.razor b/src/MudBlazor.Docs/Pages/Components/Charts/Examples/BarExample1.razor
index 0e11f5bfcafa..15027c7814d4 100644
--- a/src/MudBlazor.Docs/Pages/Components/Charts/Examples/BarExample1.razor
+++ b/src/MudBlazor.Docs/Pages/Components/Charts/Examples/BarExample1.razor
@@ -9,7 +9,7 @@
Selected: @(_index < 0 ? "None" : _series[_index].Name)
-
+
@@ -19,14 +19,8 @@
- Label Extra Height
-
-
-
-
-
- Label Rotation
-
+ XAxis Label Rotation
+
diff --git a/src/MudBlazor.Docs/Pages/Components/Charts/Examples/DonutCustomGraphicsExample.razor b/src/MudBlazor.Docs/Pages/Components/Charts/Examples/DonutCustomGraphicsExample.razor
index 67efb61c2e3f..f2daeb8d9b29 100644
--- a/src/MudBlazor.Docs/Pages/Components/Charts/Examples/DonutCustomGraphicsExample.razor
+++ b/src/MudBlazor.Docs/Pages/Components/Charts/Examples/DonutCustomGraphicsExample.razor
@@ -2,12 +2,12 @@
- Total
- @data.Sum().ToString()
+ Total
+ @data.Sum().ToString()
@code {
public double[] data = { 25, 77, 28, 5 };
public string[] labels = { "Oil", "Coal", "Gas", "Biomass" };
-}
\ No newline at end of file
+}
diff --git a/src/MudBlazor.Docs/Pages/Components/Charts/Examples/DonutExample1.razor b/src/MudBlazor.Docs/Pages/Components/Charts/Examples/DonutExample1.razor
index d54c4c947107..ce475a470fae 100644
--- a/src/MudBlazor.Docs/Pages/Components/Charts/Examples/DonutExample1.razor
+++ b/src/MudBlazor.Docs/Pages/Components/Charts/Examples/DonutExample1.razor
@@ -46,4 +46,4 @@
RandomizeData();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/MudBlazor.Docs/Pages/Components/Charts/Examples/LineExample1.razor b/src/MudBlazor.Docs/Pages/Components/Charts/Examples/LineExample1.razor
index a525f22243f7..4b8ebf1f5508 100644
--- a/src/MudBlazor.Docs/Pages/Components/Charts/Examples/LineExample1.razor
+++ b/src/MudBlazor.Docs/Pages/Components/Charts/Examples/LineExample1.razor
@@ -8,34 +8,28 @@
Selected: @(_index < 0 ? "None" : _series[_index].Name)
-
-
-
-
+
- Line Width: @_options.LineStrokeWidth.ToString()
+
-
+ Line Width: @_options.LineStrokeWidth.ToString()
-
- Label Extra Height
-
-
+
- Label Rotation
-
+ XAxis Label Rotation
+
diff --git a/src/MudBlazor.Docs/Pages/Components/Charts/Examples/PieExample1.razor b/src/MudBlazor.Docs/Pages/Components/Charts/Examples/PieExample1.razor
index 08cca2c9e25f..792df4e1668e 100644
--- a/src/MudBlazor.Docs/Pages/Components/Charts/Examples/PieExample1.razor
+++ b/src/MudBlazor.Docs/Pages/Components/Charts/Examples/PieExample1.razor
@@ -13,7 +13,7 @@
@code {
private int Index = -1; //default value cannot be 0 -> first selectedindex is 0.
int dataSize = 4;
- double[] data = { 77, 25, 20, 5 };
+ double[] data = { 77 };
string[] labels = { "Uranium", "Plutonium", "Thorium", "Caesium", "Technetium", "Promethium",
"Polonium", "Astatine", "Radon", "Francium", "Radium", "Actinium", "Protactinium",
"Neptunium", "Americium", "Curium", "Berkelium", "Californium", "Einsteinium", "Mudblaznium" };
@@ -46,4 +46,4 @@
RandomizeData();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/MudBlazor.Docs/Pages/Components/Charts/Examples/StackedBarExample1.razor b/src/MudBlazor.Docs/Pages/Components/Charts/Examples/StackedBarExample1.razor
index 665a7bb2ecac..80daba05722e 100644
--- a/src/MudBlazor.Docs/Pages/Components/Charts/Examples/StackedBarExample1.razor
+++ b/src/MudBlazor.Docs/Pages/Components/Charts/Examples/StackedBarExample1.razor
@@ -9,7 +9,7 @@
Selected: @(_index < 0 ? "None" : _series[_index].Name)
-
+
@@ -19,14 +19,8 @@
- Label Extra Height
-
-
-
-
-
- Label Rotation
-
+ XAxis Label Rotation
+
diff --git a/src/MudBlazor.Docs/Pages/Components/Charts/Examples/TimeSeriesExample1.razor b/src/MudBlazor.Docs/Pages/Components/Charts/Examples/TimeSeriesExample1.razor
index a7fdeebe71f3..1f9a09bcec89 100644
--- a/src/MudBlazor.Docs/Pages/Components/Charts/Examples/TimeSeriesExample1.razor
+++ b/src/MudBlazor.Docs/Pages/Components/Charts/Examples/TimeSeriesExample1.razor
@@ -19,37 +19,31 @@
Selected: @(_index < 0 ? "None" : _series[_index].Name)
- Line Width: @_options.LineStrokeWidth.ToString()
+ Line Width: @_options.LineStrokeWidth.ToString()
-
+
-
-
-
-
+
-
+
-
- Label Extra Height
-
-
+
- Label Rotation
-
+ XAxis Label Rotation
+
-
+
@@ -66,7 +60,7 @@
LineStrokeWidth = 1,
};
- private AxisChartOptions _axisChartOptions = new();
+ private AxisChartOptions _axisChartOptions = new() { };
private TimeSeriesChartSeries _chart1 = new();
private TimeSeriesChartSeries _chart2 = new();
diff --git a/src/MudBlazor.UnitTests/Components/Charts/BarChartTests.cs b/src/MudBlazor.UnitTests/Components/Charts/BarChartTests.cs
index 4b5741348b9a..bc2bfa990ead 100644
--- a/src/MudBlazor.UnitTests/Components/Charts/BarChartTests.cs
+++ b/src/MudBlazor.UnitTests/Components/Charts/BarChartTests.cs
@@ -86,16 +86,17 @@ public void BarChartExampleData()
Contain("United States").And.Contain("Germany").And.Contain("Sweden");
}
- if (chartSeries.Count == 3 && chartSeries.Any(x => x.Data.Contains(40)))
+ var bars = comp.FindAll("path.mud-chart-bar");
+ if (chartSeries.TryGetIndexOfDataValue(0, 40, out var index))
{
- comp.Markup.Should()
- .Contain("d=\"M 30 265 L 30 145\"");
+ bars[index].OuterHtml.Should()
+ .Contain("d=\"M 34 261 L 34 143\"");
}
- if (chartSeries.Count == 3 && chartSeries.Any(x => x.Data.Contains(80)))
+ if (chartSeries.TryGetIndexOfDataValue(0, 80, out index))
{
- comp.Markup.Should()
- .Contain("d=\"M 546.25 265 L 546.25 25\"");
+ bars[index].OuterHtml.Should()
+ .Contain("d=\"M 525.75 261 L 525.75 25\"");
}
comp.SetParametersAndRender(parameters => parameters
@@ -141,16 +142,17 @@ public void BarChartExampleSingleXAxis()
Contain("United States").And.Contain("Germany").And.Contain("Sweden");
}
- if (chartSeries.Count == 3 && chartSeries.Any(x => x.Data.Contains(40)))
+ var bars = comp.FindAll("path.mud-chart-bar");
+ if (chartSeries.TryGetIndexOfDataValue(0, 40, out var index))
{
- comp.Markup.Should()
- .Contain("d=\"M 30 265 L 30 145\"");
+ bars[index].OuterHtml.Should()
+ .Contain("d=\"M 34 261 L 34 143\"");
}
- if (chartSeries.Count == 3 && chartSeries.Any(x => x.Data.Contains(80)))
+ if (chartSeries.TryGetIndexOfDataValue(0, 80, out index))
{
- comp.Markup.Should()
- .Contain("d=\"M 546.25 265 L 546.25 25\"");
+ bars[index].OuterHtml.Should()
+ .Contain("d=\"M 525.75 261 L 525.75 25\"");
}
comp.SetParametersAndRender(parameters => parameters.Add(p => p.ChartOptions, new ChartOptions() { ChartPalette = _modifiedPalette }));
diff --git a/src/MudBlazor.UnitTests/Components/Charts/ChartSeriesExtensions.cs b/src/MudBlazor.UnitTests/Components/Charts/ChartSeriesExtensions.cs
new file mode 100644
index 000000000000..be0e7f77e3c9
--- /dev/null
+++ b/src/MudBlazor.UnitTests/Components/Charts/ChartSeriesExtensions.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MudBlazor.UnitTests.Components
+{
+ internal static class ChartSeriesExtensions
+ {
+ internal static bool TryGetIndexOfDataValue(this ChartSeries chartSeries, int seriesIndex, double value, out int dataIndex)
+ {
+ dataIndex = -1;
+
+ for (var i = 0; i < chartSeries.Data.Length; i++)
+ {
+ if (chartSeries.Data[i] == value)
+ {
+ dataIndex = i;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ internal static bool TryGetIndexOfDataValue(this IEnumerable chartSeries, int seriesIndex, double value, out int dataIndex) => TryGetIndexOfDataValue(chartSeries.ElementAt(seriesIndex), seriesIndex, value, out dataIndex);
+ }
+}
diff --git a/src/MudBlazor.UnitTests/Components/Charts/ChartToolTipTests.cs b/src/MudBlazor.UnitTests/Components/Charts/ChartToolTipTests.cs
new file mode 100644
index 000000000000..17c7060d9564
--- /dev/null
+++ b/src/MudBlazor.UnitTests/Components/Charts/ChartToolTipTests.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using FluentAssertions;
+using MudBlazor.Charts;
+using NUnit.Framework;
+
+namespace MudBlazor.UnitTests.Charts
+{
+ public class ChartToolTipTests : BunitTest
+ {
+ [SetUp]
+ public void Init()
+ {
+
+ }
+
+ [SetCulture("ru")]
+ [Test]
+ public void BarChartEmptyData()
+ {
+ var comp = Context.RenderComponent(parameters => parameters
+ .Add(p => p.Title, "Some Title")
+ .Add(p => p.Subtitle, "Some Subtitle")
+ .Add(p => p.X, 10.05)
+ .Add(p => p.Y, 20.02)
+ .Add(p => p.Color, "red")
+ );
+
+ comp.Markup.Should().Contain("");
+ comp.Markup.Should().Contain(" ");
+ comp.Markup.Should().Contain(" ");
+ comp.Markup.Should().Contain(" ");
+ comp.Markup.Should().Contain("Some Title Some Subtitle ");
+ }
+ }
+}
diff --git a/src/MudBlazor.UnitTests/Components/Charts/DonutChartTests.cs b/src/MudBlazor.UnitTests/Components/Charts/DonutChartTests.cs
index b99570e92066..2f4393ef63f4 100644
--- a/src/MudBlazor.UnitTests/Components/Charts/DonutChartTests.cs
+++ b/src/MudBlazor.UnitTests/Components/Charts/DonutChartTests.cs
@@ -164,5 +164,17 @@ public void DonutChartColoring()
}
}
}
+
+ [Test]
+ public void DonutChart100Percent()
+ {
+ double[] data = { 50, 0, 0 };
+
+ var comp = Context.RenderComponent(parameters => parameters
+ .Add(p => p.ChartType, ChartType.Donut)
+ .Add(p => p.InputData, data));
+
+ comp.Markup.Should().Contain("d=\"M 0 -140 A 140 140 0 1 1 0 140 A 140 140 0 1 1 -0 -140 L -0 -105 A 105 105 0 1 0 0 105 A 105 105 0 1 0 0 -105 Z\"");
+ }
}
}
diff --git a/src/MudBlazor.UnitTests/Components/Charts/LineChartTests.cs b/src/MudBlazor.UnitTests/Components/Charts/LineChartTests.cs
index 6b37f263141b..b2f69e22194e 100644
--- a/src/MudBlazor.UnitTests/Components/Charts/LineChartTests.cs
+++ b/src/MudBlazor.UnitTests/Components/Charts/LineChartTests.cs
@@ -83,28 +83,32 @@ public void LineChartExampleData(InterpolationOption opt)
if (chartSeries.FirstOrDefault(x => x.Name == "Series 1") is not null)
{
+ var path = comp.Find("path.mud-chart-line");
+ var d = path.GetAttribute("d");
+
switch (opt)
{
case InterpolationOption.NaturalSpline:
- comp.Markup.Should().Contain("d=\"M 30 36.5385 L 37.375 30.7688 L 44.75 25.4257 L 52.125 20.9358 L 59.5 17.7257 L 66.875 16.222 L 74.25 16.8514 L 81.625 20.0403 L 89 26.2154 L 96.375 35.8034 L 103.75 49.2308 L 111.125 66.6591 L 118.5 87.1897 L 125.875 109.6587 L 133.25 132.9024 L 140.625 155.757 L 148 177.0588 L 155.375 195.6439 L 162.75 210.3485 L 170.125 220.009 L 177.5 223.4615 L 184.875 219.9425 L 192.25 210.2895 L 199.625 195.7403 L 207 177.5324 L 214.375 156.9038 L 221.75 135.0921 L 229.125 113.335 L 236.5 92.8704 L 243.875 74.9359 L 251.25 60.7692 L 258.625 51.2784 L 266 46.0522 L 273.375 44.3495 L 280.75 45.4294 L 288.125 48.5509 L 295.5 52.9729 L 302.875 57.9545 L 310.25 62.7545 L 317.625 66.6321 L 325 68.8462 L 332.375 68.8881 L 339.75 67.1787 L 347.125 64.3713 L 354.5 61.1191 L 361.875 58.0753 L 369.25 55.8932 L 376.625 55.226 L 384 56.7269 L 391.375 61.0492 L 398.75 68.8462 L 406.125 80.4932 L 413.5 95.2546 L 420.875 112.1169 L 428.25 130.0666 L 435.625 148.0903 L 443 165.1744 L 450.375 180.3055 L 457.75 192.4702 L 465.125 200.6549 L 472.5 203.8462 L 479.875 201.3577 L 487.25 193.8123 L 494.625 182.1597 L 502 167.35 L 509.375 150.3329 L 516.75 132.0584 L 524.125 113.4765 L 531.5 95.5369 L 538.875 79.1897 L 546.25 65.3846 L 553.625 54.8552 L 561 47.4686 L 568.375 42.8758 L 575.75 40.7274 L 583.125 40.6743 L 590.5 42.3672 L 597.875 45.457 L 605.25 49.5945 L 612.625 54.4303 L 620 59.6154\"");
+ d.Should().Contain("M 30 36.3462 L 37.375 30.6726 L 44.75 25.4186 L 52.125 21.0035 L 59.5 17.8469 L 66.875 16.3683 L 74.25 16.9872 L 81.625 20.123 L 89 26.1952 L 96.375 35.6233 L 103.75 48.8269 L 111.125 65.9648 L 118.5 86.1532 L 125.875 108.2477 L 133.25 131.104 L 140.625 153.5777 L 148 174.5244 L 155.375 192.7998 L 162.75 207.2594 L 170.125 216.7589 L 177.5 220.1538 L 184.875 216.6935 L 192.25 207.2014 L 199.625 192.8946 L 207 174.9902 L 214.375 154.7054 L 221.75 133.2572 L 229.125 111.8628 L 236.5 91.7392 L 243.875 74.1036 L 251.25 60.1731 L 258.625 50.8404 L 266 45.7013 L 273.375 44.027 L 280.75 45.0889 L 288.125 48.1584 L 295.5 52.5067 L 302.875 57.4052 L 310.25 62.1253 L 317.625 65.9382 L 325 68.1154 L 332.375 68.1566 L 339.75 66.4757 L 347.125 63.7151 L 354.5 60.5171 L 361.875 57.524 L 369.25 55.3783 L 376.625 54.7222 L 384 56.1981 L 391.375 60.4484 L 398.75 68.1154 L 406.125 79.5683 L 413.5 94.0837 L 420.875 110.6649 L 428.25 128.3155 L 435.625 146.0388 L 443 162.8382 L 450.375 177.7171 L 457.75 189.679 L 465.125 197.7273 L 472.5 200.8654 L 479.875 198.4184 L 487.25 190.9987 L 494.625 179.5404 L 502 164.9775 L 509.375 148.244 L 516.75 130.2741 L 524.125 112.0019 L 531.5 94.3613 L 538.875 78.2865 L 546.25 64.7115 L 553.625 54.3576 L 561 47.0941 L 568.375 42.5778 L 575.75 40.4653 L 583.125 40.4131 L 590.5 42.0778 L 597.875 45.1161 L 605.25 49.1846 L 612.625 53.9398 L 620 59.0385");
break;
case InterpolationOption.Straight:
- comp.Markup.Should()
- .Contain("d=\"M 30 36.5385 L 103.75 49.2308 L 177.5 223.4615 L 251.25 60.7692 L 325 68.8462 L 398.75 68.8462 L 472.5 203.8462 L 546.25 65.3846 L 620 59.6154\"");
+ d.Should().Contain("M 30 36.3462 L 103.75 48.8269 L 177.5 220.1538 L 251.25 60.1731 L 325 68.1154 L 398.75 68.1154 L 472.5 200.8654 L 546.25 64.7115 L 620 59.0385");
break;
case InterpolationOption.EndSlope:
- comp.Markup.Should().Contain("d=\"M 30 36.5385 L 37.375 35.6406 L 44.75 33.4025 L 52.125 30.5074 L 59.5 27.6384 L 66.875 25.4787 L 74.25 24.7115 L 81.625 26.0199 L 89 30.0871 L 96.375 37.5964 L 103.75 49.2308 L 111.125 65.3542 L 118.5 85.0535 L 125.875 107.0958 L 133.25 130.2487 L 140.625 153.2795 L 148 174.9557 L 155.375 194.0446 L 162.75 209.3136 L 170.125 219.5301 L 177.5 223.4615 L 184.875 220.2901 L 192.25 210.8575 L 199.625 196.4201 L 207 178.2344 L 214.375 157.557 L 221.75 135.6442 L 229.125 113.7525 L 236.5 93.1385 L 243.875 75.0586 L 251.25 60.7692 L 258.625 51.193 L 266 45.9166 L 273.375 44.193 L 280.75 45.2751 L 288.125 48.4156 L 295.5 52.8676 L 302.875 57.8837 L 310.25 62.7169 L 317.625 66.6201 L 325 68.8462 L 332.375 68.8822 L 339.75 67.1529 L 347.125 64.3174 L 354.5 61.0344 L 361.875 57.9631 L 369.25 55.7625 L 376.625 55.0915 L 384 56.6091 L 391.375 60.9743 L 398.75 68.8462 L 406.125 80.602 L 413.5 95.4931 L 420.875 112.4891 L 428.25 130.5596 L 435.625 148.6741 L 443 165.8025 L 450.375 180.9142 L 457.75 192.979 L 465.125 200.9664 L 472.5 203.8462 L 479.875 200.9281 L 487.25 192.8838 L 494.625 180.7247 L 502 165.4628 L 509.375 148.1095 L 516.75 129.6768 L 524.125 111.1762 L 531.5 93.6195 L 538.875 78.0184 L 546.25 65.3846 L 553.625 56.4646 L 561 50.9441 L 568.375 48.2435 L 575.75 47.7833 L 583.125 48.9839 L 590.5 51.2658 L 597.875 54.0494 L 605.25 56.7553 L 612.625 58.8038 L 620 59.6154\"");
+ d.Should().Contain("M 30 36.3462 L 37.375 35.4633 L 44.75 33.2625 L 52.125 30.4156 L 59.5 27.5944 L 66.875 25.4707 L 74.25 24.7163 L 81.625 26.0029 L 89 30.0023 L 96.375 37.3864 L 103.75 48.8269 L 111.125 64.6817 L 118.5 84.0526 L 125.875 105.7275 L 133.25 128.4946 L 140.625 151.1415 L 148 172.4564 L 155.375 191.2272 L 162.75 206.2417 L 170.125 216.2879 L 177.5 220.1538 L 184.875 217.0353 L 192.25 207.7599 L 199.625 193.5631 L 207 175.6805 L 214.375 155.3477 L 221.75 133.8001 L 229.125 112.2733 L 236.5 92.0029 L 243.875 74.2243 L 251.25 60.1731 L 258.625 50.7564 L 266 45.568 L 273.375 43.8732 L 280.75 44.9372 L 288.125 48.0254 L 295.5 52.4031 L 302.875 57.3356 L 310.25 62.0883 L 317.625 65.9265 L 325 68.1154 L 332.375 68.1508 L 339.75 66.4504 L 347.125 63.6621 L 354.5 60.4338 L 361.875 57.4137 L 369.25 55.2498 L 376.625 54.59 L 384 56.0823 L 391.375 60.3747 L 398.75 68.1154 L 406.125 79.6753 L 413.5 94.3182 L 420.875 111.0309 L 428.25 128.8002 L 435.625 146.6129 L 443 163.4558 L 450.375 178.3157 L 457.75 190.1794 L 465.125 198.0337 L 472.5 200.8654 L 479.875 197.996 L 487.25 190.0857 L 494.625 178.1293 L 502 163.1217 L 509.375 146.0577 L 516.75 127.9322 L 524.125 109.7399 L 531.5 92.4759 L 538.875 77.1348 L 546.25 64.7115 L 553.625 55.9402 L 561 50.5117 L 568.375 47.8561 L 575.75 47.4035 L 583.125 48.5841 L 590.5 50.828 L 597.875 53.5652 L 605.25 56.226 L 612.625 58.2404 L 620 59.0385");
break;
case InterpolationOption.Periodic:
- comp.Markup.Should().Contain("d=\"M 30 36.5385 L 37.375 36.3538 L 44.75 34.5703 L 52.125 31.9087 L 59.5 29.0896 L 66.875 26.8338 L 74.25 25.8621 L 81.625 26.8952 L 89 30.6538 L 96.375 37.8588 L 103.75 49.2308 L 111.125 65.1633 L 118.5 84.7409 L 125.875 106.7209 L 133.25 129.8605 L 140.625 152.9172 L 148 174.6482 L 155.375 193.8109 L 162.75 209.1624 L 170.125 219.4602 L 177.5 223.4615 L 184.875 220.3407 L 192.25 210.94 L 199.625 196.5187 L 207 178.3359 L 214.375 157.6511 L 221.75 135.7234 L 229.125 113.8121 L 236.5 93.1765 L 243.875 75.0758 L 251.25 60.7692 L 258.625 51.1816 L 266 45.8991 L 273.375 44.1738 L 280.75 45.2573 L 288.125 48.4014 L 295.5 52.8581 L 302.875 57.8791 L 310.25 62.7163 L 317.625 66.6213 L 325 68.8462 L 332.375 68.8773 L 339.75 67.1404 L 347.125 64.296 L 354.5 61.0043 L 361.875 57.9258 L 369.25 55.721 L 376.625 55.0502 L 384 56.5738 L 391.375 60.9524 L 398.75 68.8462 L 406.125 80.6331 L 413.5 95.5607 L 420.875 112.5939 L 428.25 130.6979 L 435.625 148.8376 L 443 165.9779 L 450.375 181.0839 L 457.75 193.1207 L 465.125 201.0531 L 472.5 203.8462 L 479.875 200.8089 L 487.25 192.6262 L 494.625 180.3267 L 502 164.9395 L 509.375 147.4931 L 516.75 129.0166 L 524.125 110.5387 L 531.5 93.0881 L 538.875 77.6938 L 546.25 65.3846 L 553.625 56.9106 L 561 51.907 L 568.375 49.7307 L 575.75 49.7381 L 583.125 51.2861 L 590.5 53.7311 L 597.875 56.4299 L 605.25 58.7391 L 612.625 60.0154 L 620 59.6154\"");
+ d.Should().Contain("M 30 36.3462 L 37.375 36.1646 L 44.75 34.4108 L 52.125 31.7935 L 59.5 29.0214 L 66.875 26.8032 L 74.25 25.8477 L 81.625 26.8636 L 89 30.5596 L 96.375 37.6445 L 103.75 48.8269 L 111.125 64.4939 L 118.5 83.7452 L 125.875 105.3589 L 133.25 128.1129 L 140.625 150.7853 L 148 172.1541 L 155.375 190.9974 L 162.75 206.093 L 170.125 216.2192 L 177.5 220.1538 L 184.875 217.085 L 192.25 207.841 L 199.625 193.66 L 207 175.7803 L 214.375 155.4402 L 221.75 133.878 L 229.125 112.3319 L 236.5 92.0402 L 243.875 74.2412 L 251.25 60.1731 L 258.625 50.7452 L 266 45.5508 L 273.375 43.8542 L 280.75 44.9196 L 288.125 48.0114 L 295.5 52.3938 L 302.875 57.3311 L 310.25 62.0877 L 317.625 65.9276 L 325 68.1154 L 332.375 68.146 L 339.75 66.4381 L 347.125 63.641 L 354.5 60.4042 L 361.875 57.3771 L 369.25 55.209 L 376.625 54.5494 L 384 56.0476 L 391.375 60.3532 L 398.75 68.1154 L 406.125 79.7058 L 413.5 94.3846 L 420.875 111.134 L 428.25 128.9363 L 435.625 146.7736 L 443 163.6283 L 450.375 178.4825 L 457.75 190.3186 L 465.125 198.1188 L 472.5 200.8654 L 479.875 197.8788 L 487.25 189.8324 L 494.625 177.738 L 502 162.6071 L 509.375 145.4516 L 516.75 127.283 L 524.125 109.113 L 531.5 91.9533 L 538.875 76.8156 L 546.25 64.7115 L 553.625 56.3787 L 561 51.4586 L 568.375 49.3185 L 575.75 49.3258 L 583.125 50.848 L 590.5 53.2522 L 597.875 55.9061 L 605.25 58.1768 L 612.625 59.4318 L 620 59.0385");
break;
}
}
if (comp.Instance.ChartOptions.InterpolationOption == InterpolationOption.Straight && chartSeries.FirstOrDefault(x => x.Name == "Series 2") is not null)
{
- comp.Markup.Should()
- .Contain("d=\"M 30 128.8462 L 103.75 93.0769 L 177.5 100 L 251.25 81.5385 L 325 83.8462 L 398.75 68.8462 L 472.5 220 L 546.25 35.3846 L 620 311.1538\"");
+ var path = comp.FindAll("path.mud-chart-line").Skip(1).First();
+ var d = path.GetAttribute("d");
+
+ d.Should().Contain("M 30 127.1154 L 103.75 91.9423 L 177.5 98.75 L 251.25 80.5962 L 325 82.8654 L 398.75 68.1154 L 472.5 216.75 L 546.25 35.2115 L 620 306.3846");
}
comp.SetParametersAndRender(parameters => parameters
@@ -169,20 +173,22 @@ public void LineChartExampleZeroValues(InterpolationOption opt)
comp.Markup.Should().Contain("mud-chart-legend-item");
comp.Markup.Should().Contain("Series 1");
+ var path = comp.Find("path.mud-chart-line");
+ var d = path.GetAttribute("d");
+
switch (opt)
{
case InterpolationOption.NaturalSpline:
- comp.Markup.Should().Contain("d=\"M 30 325 L 37.375 325 L 44.75 325 L 52.125 325 L 59.5 325 L 66.875 325 L 74.25 325 L 81.625 325 L 89 325 L 96.375 325 L 103.75 325 L 111.125 325 L 118.5 325 L 125.875 325 L 133.25 325 L 140.625 325 L 148 325 L 155.375 325 L 162.75 325 L 170.125 325 L 177.5 325 L 184.875 325 L 192.25 325 L 199.625 325 L 207 325 L 214.375 325 L 221.75 325 L 229.125 325 L 236.5 325 L 243.875 325 L 251.25 325 L 258.625 325 L 266 325 L 273.375 325 L 280.75 325 L 288.125 325 L 295.5 325 L 302.875 325 L 310.25 325 L 317.625 325 L 325 325 L 332.375 325 L 339.75 325 L 347.125 325 L 354.5 325 L 361.875 325 L 369.25 325 L 376.625 325 L 384 325 L 391.375 325 L 398.75 325 L 406.125 325 L 413.5 325 L 420.875 325 L 428.25 325 L 435.625 325 L 443 325 L 450.375 325 L 457.75 325 L 465.125 325 L 472.5 325 L 479.875 325 L 487.25 325 L 494.625 325 L 502 325 L 509.375 325 L 516.75 325 L 524.125 325 L 531.5 325 L 538.875 325 L 546.25 325 L 553.625 325 L 561 325 L 568.375 325 L 575.75 325 L 583.125 325 L 590.5 325 L 597.875 325 L 605.25 325 L 612.625 325 L 620 325\"");
+ d.Should().Contain("M 30 320 L 37.375 320 L 44.75 320 L 52.125 320 L 59.5 320 L 66.875 320 L 74.25 320 L 81.625 320 L 89 320 L 96.375 320 L 103.75 320 L 111.125 320 L 118.5 320 L 125.875 320 L 133.25 320 L 140.625 320 L 148 320 L 155.375 320 L 162.75 320 L 170.125 320 L 177.5 320 L 184.875 320 L 192.25 320 L 199.625 320 L 207 320 L 214.375 320 L 221.75 320 L 229.125 320 L 236.5 320 L 243.875 320 L 251.25 320 L 258.625 320 L 266 320 L 273.375 320 L 280.75 320 L 288.125 320 L 295.5 320 L 302.875 320 L 310.25 320 L 317.625 320 L 325 320 L 332.375 320 L 339.75 320 L 347.125 320 L 354.5 320 L 361.875 320 L 369.25 320 L 376.625 320 L 384 320 L 391.375 320 L 398.75 320 L 406.125 320 L 413.5 320 L 420.875 320 L 428.25 320 L 435.625 320 L 443 320 L 450.375 320 L 457.75 320 L 465.125 320 L 472.5 320 L 479.875 320 L 487.25 320 L 494.625 320 L 502 320 L 509.375 320 L 516.75 320 L 524.125 320 L 531.5 320 L 538.875 320 L 546.25 320 L 553.625 320 L 561 320 L 568.375 320 L 575.75 320 L 583.125 320 L 590.5 320 L 597.875 320 L 605.25 320 L 612.625 320 L 620 320");
break;
case InterpolationOption.Straight:
- comp.Markup.Should()
- .Contain("d=\"M 30 325 L 103.75 325 L 177.5 325 L 251.25 325 L 325 325 L 398.75 325 L 472.5 325 L 546.25 325 L 620 325\"");
+ d.Should().Contain("M 30 320 L 103.75 320 L 177.5 320 L 251.25 320 L 325 320 L 398.75 320 L 472.5 320 L 546.25 320 L 620 320");
break;
case InterpolationOption.EndSlope:
- comp.Markup.Should().Contain("d=\"M 30 325 L 37.375 325 L 44.75 325 L 52.125 325 L 59.5 325 L 66.875 325 L 74.25 325 L 81.625 325 L 89 325 L 96.375 325 L 103.75 325 L 111.125 325 L 118.5 325 L 125.875 325 L 133.25 325 L 140.625 325 L 148 325 L 155.375 325 L 162.75 325 L 170.125 325 L 177.5 325 L 184.875 325 L 192.25 325 L 199.625 325 L 207 325 L 214.375 325 L 221.75 325 L 229.125 325 L 236.5 325 L 243.875 325 L 251.25 325 L 258.625 325 L 266 325 L 273.375 325 L 280.75 325 L 288.125 325 L 295.5 325 L 302.875 325 L 310.25 325 L 317.625 325 L 325 325 L 332.375 325 L 339.75 325 L 347.125 325 L 354.5 325 L 361.875 325 L 369.25 325 L 376.625 325 L 384 325 L 391.375 325 L 398.75 325 L 406.125 325 L 413.5 325 L 420.875 325 L 428.25 325 L 435.625 325 L 443 325 L 450.375 325 L 457.75 325 L 465.125 325 L 472.5 325 L 479.875 325 L 487.25 325 L 494.625 325 L 502 325 L 509.375 325 L 516.75 325 L 524.125 325 L 531.5 325 L 538.875 325 L 546.25 325 L 553.625 325 L 561 325 L 568.375 325 L 575.75 325 L 583.125 325 L 590.5 325 L 597.875 325 L 605.25 325 L 612.625 325 L 620 325\"");
+ d.Should().Contain("M 30 320 L 37.375 320 L 44.75 320 L 52.125 320 L 59.5 320 L 66.875 320 L 74.25 320 L 81.625 320 L 89 320 L 96.375 320 L 103.75 320 L 111.125 320 L 118.5 320 L 125.875 320 L 133.25 320 L 140.625 320 L 148 320 L 155.375 320 L 162.75 320 L 170.125 320 L 177.5 320 L 184.875 320 L 192.25 320 L 199.625 320 L 207 320 L 214.375 320 L 221.75 320 L 229.125 320 L 236.5 320 L 243.875 320 L 251.25 320 L 258.625 320 L 266 320 L 273.375 320 L 280.75 320 L 288.125 320 L 295.5 320 L 302.875 320 L 310.25 320 L 317.625 320 L 325 320 L 332.375 320 L 339.75 320 L 347.125 320 L 354.5 320 L 361.875 320 L 369.25 320 L 376.625 320 L 384 320 L 391.375 320 L 398.75 320 L 406.125 320 L 413.5 320 L 420.875 320 L 428.25 320 L 435.625 320 L 443 320 L 450.375 320 L 457.75 320 L 465.125 320 L 472.5 320 L 479.875 320 L 487.25 320 L 494.625 320 L 502 320 L 509.375 320 L 516.75 320 L 524.125 320 L 531.5 320 L 538.875 320 L 546.25 320 L 553.625 320 L 561 320 L 568.375 320 L 575.75 320 L 583.125 320 L 590.5 320 L 597.875 320 L 605.25 320 L 612.625 320 L 620 320");
break;
case InterpolationOption.Periodic:
- comp.Markup.Should().Contain("d=\"M 30 325 L 37.375 325 L 44.75 325 L 52.125 325 L 59.5 325 L 66.875 325 L 74.25 325 L 81.625 325 L 89 325 L 96.375 325 L 103.75 325 L 111.125 325 L 118.5 325 L 125.875 325 L 133.25 325 L 140.625 325 L 148 325 L 155.375 325 L 162.75 325 L 170.125 325 L 177.5 325 L 184.875 325 L 192.25 325 L 199.625 325 L 207 325 L 214.375 325 L 221.75 325 L 229.125 325 L 236.5 325 L 243.875 325 L 251.25 325 L 258.625 325 L 266 325 L 273.375 325 L 280.75 325 L 288.125 325 L 295.5 325 L 302.875 325 L 310.25 325 L 317.625 325 L 325 325 L 332.375 325 L 339.75 325 L 347.125 325 L 354.5 325 L 361.875 325 L 369.25 325 L 376.625 325 L 384 325 L 391.375 325 L 398.75 325 L 406.125 325 L 413.5 325 L 420.875 325 L 428.25 325 L 435.625 325 L 443 325 L 450.375 325 L 457.75 325 L 465.125 325 L 472.5 325 L 479.875 325 L 487.25 325 L 494.625 325 L 502 325 L 509.375 325 L 516.75 325 L 524.125 325 L 531.5 325 L 538.875 325 L 546.25 325 L 553.625 325 L 561 325 L 568.375 325 L 575.75 325 L 583.125 325 L 590.5 325 L 597.875 325 L 605.25 325 L 612.625 325 L 620 325\"");
+ d.Should().Contain("M 30 320 L 37.375 320 L 44.75 320 L 52.125 320 L 59.5 320 L 66.875 320 L 74.25 320 L 81.625 320 L 89 320 L 96.375 320 L 103.75 320 L 111.125 320 L 118.5 320 L 125.875 320 L 133.25 320 L 140.625 320 L 148 320 L 155.375 320 L 162.75 320 L 170.125 320 L 177.5 320 L 184.875 320 L 192.25 320 L 199.625 320 L 207 320 L 214.375 320 L 221.75 320 L 229.125 320 L 236.5 320 L 243.875 320 L 251.25 320 L 258.625 320 L 266 320 L 273.375 320 L 280.75 320 L 288.125 320 L 295.5 320 L 302.875 320 L 310.25 320 L 317.625 320 L 325 320 L 332.375 320 L 339.75 320 L 347.125 320 L 354.5 320 L 361.875 320 L 369.25 320 L 376.625 320 L 384 320 L 391.375 320 L 398.75 320 L 406.125 320 L 413.5 320 L 420.875 320 L 428.25 320 L 435.625 320 L 443 320 L 450.375 320 L 457.75 320 L 465.125 320 L 472.5 320 L 479.875 320 L 487.25 320 L 494.625 320 L 502 320 L 509.375 320 L 516.75 320 L 524.125 320 L 531.5 320 L 538.875 320 L 546.25 320 L 553.625 320 L 561 320 L 568.375 320 L 575.75 320 L 583.125 320 L 590.5 320 L 597.875 320 L 605.25 320 L 612.625 320 L 620 320");
break;
}
diff --git a/src/MudBlazor.UnitTests/Components/Charts/PieChartTests.cs b/src/MudBlazor.UnitTests/Components/Charts/PieChartTests.cs
index 821e8ca5803a..f103e6a86a90 100644
--- a/src/MudBlazor.UnitTests/Components/Charts/PieChartTests.cs
+++ b/src/MudBlazor.UnitTests/Components/Charts/PieChartTests.cs
@@ -78,17 +78,16 @@ public void PieChartExampleData(double[] data)
comp.Markup.Should()
.Contain("Technetium");
}
-
if (data.Length == 4 && data.Contains(77))
{
comp.Markup.Should()
- .Contain("d=\"M 0 -140 A 140 140 0 1 1 -86.7071 109.9176 L 0 0\"");
+ .Contain("d=\"M 0 -140 A 140 140 0 1 1 -86.7071 109.9176 L 0 0 Z\"");
}
if (data.Length == 4 && data.Contains(5))
{
comp.Markup.Should()
- .Contain("d=\"M -34.2796 -135.7384 A 140 140 0 0 1 -0 -140 L 0 0\"");
+ .Contain("d=\"M -34.2796 -135.7384 A 140 140 0 0 1 -0 -140 L 0 0 Z\"");
}
comp.SetParametersAndRender(parameters => parameters
@@ -133,5 +132,17 @@ public void PieChartColoring()
}
}
}
+
+ [Test]
+ public void PieChart100Percent()
+ {
+ double[] data = { 50, 0, 0 };
+
+ var comp = Context.RenderComponent(parameters => parameters
+ .Add(p => p.ChartType, ChartType.Pie)
+ .Add(p => p.InputData, data));
+
+ comp.Markup.Should().Contain("d=\"M 0 -140 A 140 140 0 1 1 0 140 A 140 140 0 1 1 -0 -140 L 0 0 Z\"");
+ }
}
}
diff --git a/src/MudBlazor.UnitTests/Components/Charts/StackedBarChartTests.cs b/src/MudBlazor.UnitTests/Components/Charts/StackedBarChartTests.cs
index 5a4c481838ea..2de131cf4fe6 100644
--- a/src/MudBlazor.UnitTests/Components/Charts/StackedBarChartTests.cs
+++ b/src/MudBlazor.UnitTests/Components/Charts/StackedBarChartTests.cs
@@ -88,10 +88,10 @@ public void BarChartExampleData()
}
comp.Markup.Should()
- .Contain("d=\"M 62.9 325 L 62.9 224.5\"");
+ .Contain("d=\"M 62.9 320 L 62.9 221.1667\"");
comp.Markup.Should()
- .Contain("d=\"M 587.7 210 L 587.7 164.5\"");
+ .Contain("d=\"M 587.7 206.9167 L 587.7 162.1667\"");
comp.SetParametersAndRender(parameters => parameters
.Add(p => p.ChartOptions, new ChartOptions() { ChartPalette = _modifiedPalette }));
diff --git a/src/MudBlazor.UnitTests/Components/Charts/TimeSeriesChartTests.cs b/src/MudBlazor.UnitTests/Components/Charts/TimeSeriesChartTests.cs
index 52507598734a..e6cb5f7eb8ec 100644
--- a/src/MudBlazor.UnitTests/Components/Charts/TimeSeriesChartTests.cs
+++ b/src/MudBlazor.UnitTests/Components/Charts/TimeSeriesChartTests.cs
@@ -37,10 +37,12 @@ public void TimeSeriesChartBasicExample()
.Add(p => p.TimeLabelSpacing, TimeSpan.FromHours(1)));
// check the line path
- comp.Markup.Should().ContainEquivalentOf(" ");
+ comp.Markup.Should().ContainEquivalentOf(" ");
// check the axis
- comp.Markup.Should().ContainEquivalentOf(" \n 1000 \n 23:00 00:00 01:00 ");
+ comp.Markup.Should().ContainEquivalentOf(" ");
+ comp.Markup.Should().ContainEquivalentOf("1000 ");
+ comp.Markup.Should().ContainEquivalentOf("23:00 00:00 01:00 02:00 ");
}
[Test]
@@ -88,7 +90,8 @@ public void TimeSeriesChartTimeLabelSpacingRounding()
.Add(p => p.TimeLabelSpacingRounding, true));
// check the axis
- comp.Markup.Should().ContainEquivalentOf(" \n 1000 \n 00:00 01:00 ");
+ comp.Markup.Should().ContainEquivalentOf("1000 ");
+ comp.Markup.Should().ContainEquivalentOf("00:00 01:00 02:00 ");
}
[Test]
@@ -112,10 +115,11 @@ public void TimeSeriesChartTimeLabelSpacingRoundingPadSeries()
.Add(p => p.TimeLabelSpacingRoundingPadSeries, true));
// check the axis
- comp.Markup.Should().ContainEquivalentOf(" \n 1000 \n 23:00 00:00 01:00 02:00 ");
+ comp.Markup.Should().ContainEquivalentOf("1000 ");
+ comp.Markup.Should().ContainEquivalentOf("23:00 00:00 01:00 02:00 03:00 ");
// check the line path
- comp.Markup.Should().ContainEquivalentOf(" ");
+ comp.Markup.Should().ContainEquivalentOf("d=\"M 60.8333 320 L 245.8333 320 L 430.8333 320 L 615.8333 320\"");
}
[Test]
diff --git a/src/MudBlazor/Components/Chart/Charts/Bar.razor b/src/MudBlazor/Components/Chart/Charts/Bar.razor
index c2f9536e9b41..3d784760ca35 100644
--- a/src/MudBlazor/Components/Chart/Charts/Bar.razor
+++ b/src/MudBlazor/Components/Chart/Charts/Bar.razor
@@ -15,7 +15,7 @@
}
- @if (MudChartParent?.ChartOptions.XAxisLines==true)
+ @if (MudChartParent?.ChartOptions.XAxisLines == true)
{
@foreach (var verticalLine in _verticalLines)
@@ -25,19 +25,19 @@
}
-
+
@foreach (var horizontalLineValue in _horizontalValues)
{
@((MarkupString)$"{horizontalLineValue.Value?.ToString(CultureInfo.InvariantCulture)} ")
}
-
+
@for (var i = 0; i < _verticalValues.Count; i++)
{
var verticalLineValue = _verticalValues[i];
var x = verticalLineValue.X.ToString(CultureInfo.InvariantCulture);
var y = verticalLineValue.Y.ToString(CultureInfo.InvariantCulture);
- var rotation = (-AxisChartOptions.LabelRotation).ToString(CultureInfo.InvariantCulture);
+ var rotation = (-AxisChartOptions.XAxisLabelRotation).ToString(CultureInfo.InvariantCulture);
@((MarkupString)$"{verticalLineValue.Value?.ToString(CultureInfo.InvariantCulture)} ")
}
@@ -50,7 +50,7 @@
@onclick="() => SelectedIndex = bar.Index"
fill="@color"
stroke="@color"
- stroke-width="8"
+ stroke-width="@BarStroke"
d="@bar.Data"
@onmouseover="(e) => OnBarMouseOver(e, bar)"
@onmouseout="OnBarMouseOut">
@@ -58,12 +58,12 @@
}
- @MudChartParent?.CustomGraphics
+ @MudChartParent?.CustomGraphics
@* Render the tooltip as an SVG group when a bar is hovered *@
- @if (_hoveredBar is not null)
+ @if (_hoveredBar is not null && MudChartParent?.ChartOptions.ShowToolTips == true)
{
- var color = MudChartParent?.ChartOptions.ChartPalette.GetValue(_hoveredBar.Index % MudChartParent.ChartOptions.ChartPalette.Length)?.ToString() ?? string.Empty;
+ var color = MudChartParent.ChartOptions.ChartPalette.GetValue(_hoveredBar.Index % MudChartParent.ChartOptions.ChartPalette.Length)?.ToString() ?? string.Empty;
var series = _series[_hoveredBar.Index];
if (!string.IsNullOrWhiteSpace(series.DataMarkerTooltipTitleFormat))
diff --git a/src/MudBlazor/Components/Chart/Charts/Bar.razor.cs b/src/MudBlazor/Components/Chart/Charts/Bar.razor.cs
index d90055770d86..08969cb6ac03 100644
--- a/src/MudBlazor/Components/Chart/Charts/Bar.razor.cs
+++ b/src/MudBlazor/Components/Chart/Charts/Bar.razor.cs
@@ -13,18 +13,22 @@ namespace MudBlazor.Charts
///
partial class Bar : MudCategoryAxisChartBase
{
- private List _horizontalLines = [];
- private List _horizontalValues = [];
+ private readonly List _horizontalLines = [];
+ private readonly List _horizontalValues = [];
- private List _verticalLines = [];
- private List _verticalValues = [];
+ private readonly List _verticalLines = [];
+ private readonly List _verticalValues = [];
- private List _legends = [];
+ private readonly List _legends = [];
private List _series = [];
- private List _bars = [];
+ private readonly List _bars = [];
private SvgPath? _hoveredBar;
+ private const double BarStroke = 8;
+ private const double BarGap = 10;
+ private double BarGroupWidth => (_series.Count - 1) * BarGap + BarStroke; // number of gaps of 10 + the stroke width
+
///
protected override void OnParametersSet()
{
@@ -41,8 +45,8 @@ protected override void RebuildChart()
SetBounds();
ComputeUnitsAndNumberOfLines(out var gridXUnits, out var gridYUnits, out var numHorizontalLines, out var lowestHorizontalLine, out var numVerticalLines);
- var horizontalSpace = (_boundWidth - HorizontalStartSpace - HorizontalEndSpace) / Math.Max(1, numVerticalLines - 1);
- var verticalSpace = (_boundHeight - VerticalStartSpace - VerticalEndSpace - AxisChartOptions.LabelExtraHeight) / Math.Max(1, numHorizontalLines - 1);
+ var horizontalSpace = (_boundWidth - HorizontalStartSpace - HorizontalEndSpace - BarGroupWidth) / Math.Max(1, numVerticalLines - 1);
+ var verticalSpace = (_boundHeight - VerticalStartSpace - VerticalEndSpace) / Math.Max(1, numHorizontalLines - 1);
GenerateHorizontalGridLines(numHorizontalLines, lowestHorizontalLine, gridYUnits, verticalSpace);
GenerateVerticalGridLines(numVerticalLines, gridXUnits, horizontalSpace);
@@ -66,7 +70,7 @@ private void ComputeUnitsAndNumberOfLines(out double gridXUnits, out double grid
numHorizontalLines = highestHorizontalLine - lowestHorizontalLine + 1;
// this is a safeguard against millions of gridlines which might arise with very high values
- var maxYTicks = MudChartParent?.ChartOptions.MaxNumYAxisTicks ?? 100;
+ var maxYTicks = MudChartParent?.ChartOptions.MaxNumYAxisTicks ?? 20;
while (numHorizontalLines > maxYTicks)
{
gridYUnits *= 2;
@@ -96,7 +100,7 @@ private void GenerateHorizontalGridLines(int numHorizontalLines, int lowestHoriz
var line = new SvgPath()
{
Index = i,
- Data = $"M {ToS(HorizontalStartSpace)} {ToS(_boundHeight - AxisChartOptions.LabelExtraHeight - y)} L {ToS(_boundWidth - HorizontalEndSpace)} {ToS(_boundHeight - AxisChartOptions.LabelExtraHeight - y)}"
+ Data = $"M {ToS(HorizontalStartSpace)} {ToS(_boundHeight - y)} L {ToS(_boundWidth - HorizontalEndSpace)} {ToS(_boundHeight - y)}"
};
_horizontalLines.Add(line);
@@ -104,7 +108,7 @@ private void GenerateHorizontalGridLines(int numHorizontalLines, int lowestHoriz
var lineValue = new SvgText()
{
X = HorizontalStartSpace - 10,
- Y = _boundHeight - AxisChartOptions.LabelExtraHeight - y + 5,
+ Y = _boundHeight - y + 5,
Value = ToS(startGridY, MudChartParent?.ChartOptions.YAxisFormat)
};
_horizontalValues.Add(lineValue);
@@ -122,15 +126,15 @@ private void GenerateVerticalGridLines(int numVerticalLines, double gridXUnits,
var line = new SvgPath()
{
Index = i,
- Data = $"M {ToS(x)} {ToS(_boundHeight - VerticalStartSpace - AxisChartOptions.LabelExtraHeight)} L {ToS(x)} {ToS(VerticalEndSpace)}"
+ Data = $"M {ToS(x)} {ToS(_boundHeight - VerticalStartSpace)} L {ToS(x)} {ToS(VerticalEndSpace)}"
};
_verticalLines.Add(line);
var xLabels = i < XAxisLabels.Length ? XAxisLabels[i] : "";
var lineValue = new SvgText()
{
- X = x,
- Y = _boundHeight - (AxisChartOptions.LabelExtraHeight / 2) - 10,
+ X = x + (BarGroupWidth / 2),
+ Y = _boundHeight - 10,
Value = xLabels
};
_verticalValues.Add(lineValue);
@@ -144,21 +148,22 @@ private void GenerateBars(int lowestHorizontalLine, double gridYUnits, double ho
for (var i = 0; i < _series.Count; i++)
{
- var data = _series[i].Data;
+ var series = _series[i];
+ var data = series.Data;
for (var j = 0; j < data.Length; j++)
{
- var gridValueX = HorizontalStartSpace + (i * 10) + (j * horizontalSpace);
- var gridValueY = _boundHeight - VerticalStartSpace - AxisChartOptions.LabelExtraHeight + (lowestHorizontalLine * verticalSpace);
+ var gridValueX = HorizontalStartSpace + (BarStroke / 2) + (i * BarGap) + (j * horizontalSpace);
+ var gridValueY = _boundHeight - VerticalStartSpace + (lowestHorizontalLine * verticalSpace);
var dataValue = ((data[j] / gridYUnits) - lowestHorizontalLine) * verticalSpace;
- var gridValue = _boundHeight - VerticalStartSpace - AxisChartOptions.LabelExtraHeight - dataValue;
+ var gridValue = _boundHeight - VerticalStartSpace - dataValue;
var bar = new SvgPath()
{
Index = i,
Data = $"M {ToS(gridValueX)} {ToS(gridValueY)} L {ToS(gridValueX)} {ToS(gridValue)}",
LabelXValue = XAxisLabels.Length > j ? XAxisLabels[j] : string.Empty,
- LabelYValue = dataValue.ToString(),
+ LabelYValue = dataValue.ToString(series.DataMarkerTooltipYValueFormat),
LabelX = gridValueX,
LabelY = gridValue
};
@@ -168,7 +173,7 @@ private void GenerateBars(int lowestHorizontalLine, double gridYUnits, double ho
var legend = new SvgLegend()
{
Index = i,
- Labels = _series[i].Name
+ Labels = series.Name
};
_legends.Add(legend);
}
diff --git a/src/MudBlazor/Components/Chart/Charts/Donut.razor b/src/MudBlazor/Components/Chart/Charts/Donut.razor
index 0bf8a059f55c..f5ce3b234a7f 100644
--- a/src/MudBlazor/Components/Chart/Charts/Donut.razor
+++ b/src/MudBlazor/Components/Chart/Charts/Donut.razor
@@ -1,5 +1,4 @@
@namespace MudBlazor.Charts
-@using System.Globalization
@inherits MudCategoryChartBase
diff --git a/src/MudBlazor/Components/Chart/Charts/Line.razor b/src/MudBlazor/Components/Chart/Charts/Line.razor
index c543f9cae536..cd07d877e190 100644
--- a/src/MudBlazor/Components/Chart/Charts/Line.razor
+++ b/src/MudBlazor/Components/Chart/Charts/Line.razor
@@ -31,19 +31,19 @@
}
-
+
@foreach (var horizontalLineValue in _horizontalValues)
{
@((MarkupString)$"{horizontalLineValue.Value?.ToString(CultureInfo.InvariantCulture)} ")
}
-
+
@for (var i = 0; i < _verticalValues.Count; i++)
{
var verticalLineValue = _verticalValues[i];
var x = verticalLineValue.X.ToString(CultureInfo.InvariantCulture);
var y = verticalLineValue.Y.ToString(CultureInfo.InvariantCulture);
- var rotation = (-AxisChartOptions.LabelRotation).ToString(CultureInfo.InvariantCulture);
+ var rotation = (-AxisChartOptions.XAxisLabelRotation).ToString(CultureInfo.InvariantCulture);
@((MarkupString)$"{verticalLineValue.Value?.ToString(CultureInfo.InvariantCulture)} ")
}
@@ -82,7 +82,7 @@
cx="@ToS(item.CX)"
cy="@ToS(item.CY)"
r="@ToS(dataPointHoverRadius)"
- @onmouseover="(e) => OnDataPointMouseOver(e, item)"
+ @onmouseover="(e) => OnDataPointMouseOver(e, item, chartLine)"
@onmouseout="OnDataPointMouseOut">
}
@@ -92,11 +92,11 @@
@MudChartParent?.CustomGraphics
@* Render the tooltip as an SVG group when a bar is hovered *@
- @if (_hoveredDataPoint is not null && _hoverDataPointChartLine is not null)
+ @if (_hoveredDataPoint is not null && _hoverDataPointChartLine is not null && MudChartParent?.ChartOptions.ShowToolTips == true)
{
var seriesIndex = _hoverDataPointChartLine.Index;
- var color = MudChartParent?.ChartOptions.ChartPalette.GetValue(seriesIndex % MudChartParent.ChartOptions.ChartPalette.Length)?.ToString() ?? string.Empty;
+ var color = MudChartParent.ChartOptions.ChartPalette.GetValue(seriesIndex % MudChartParent.ChartOptions.ChartPalette.Length)?.ToString() ?? string.Empty;
var series = _series[seriesIndex];
if (!string.IsNullOrWhiteSpace(series.DataMarkerTooltipTitleFormat))
diff --git a/src/MudBlazor/Components/Chart/Charts/Line.razor.cs b/src/MudBlazor/Components/Chart/Charts/Line.razor.cs
index 3ddabf2caf41..8e9a1efef6b4 100644
--- a/src/MudBlazor/Components/Chart/Charts/Line.razor.cs
+++ b/src/MudBlazor/Components/Chart/Charts/Line.razor.cs
@@ -16,18 +16,18 @@ namespace MudBlazor.Charts
///
partial class Line : MudCategoryAxisChartBase
{
- private List _horizontalLines = [];
- private List _horizontalValues = [];
+ private readonly List _horizontalLines = [];
+ private readonly List _horizontalValues = [];
- private List _verticalLines = [];
- private List _verticalValues = [];
+ private readonly List _verticalLines = [];
+ private readonly List _verticalValues = [];
- private List _legends = [];
+ private readonly List _legends = [];
private List _series = [];
- private List _chartLines = [];
- private Dictionary _chartAreas = [];
- private Dictionary> _chartDataPoints = [];
+ private readonly List _chartLines = [];
+ private readonly Dictionary _chartAreas = [];
+ private readonly Dictionary> _chartDataPoints = [];
private SvgCircle? _hoveredDataPoint;
private SvgPath? _hoverDataPointChartLine;
@@ -47,7 +47,7 @@ protected override void RebuildChart()
ComputeUnitsAndNumberOfLines(out var gridXUnits, out var gridYUnits, out var numHorizontalLines, out var lowestHorizontalLine, out var numVerticalLines);
var horizontalSpace = (_boundWidth - HorizontalStartSpace - HorizontalEndSpace) / Math.Max(1, numVerticalLines - 1);
- var verticalSpace = (_boundHeight - VerticalStartSpace - VerticalEndSpace - AxisChartOptions.LabelExtraHeight) / Math.Max(1, numHorizontalLines - 1);
+ var verticalSpace = (_boundHeight - VerticalStartSpace - VerticalEndSpace) / Math.Max(1, numHorizontalLines - 1);
GenerateHorizontalGridLines(numHorizontalLines, lowestHorizontalLine, gridYUnits, verticalSpace);
GenerateVerticalGridLines(numVerticalLines, gridXUnits, horizontalSpace);
@@ -109,7 +109,7 @@ private void GenerateHorizontalGridLines(int numHorizontalLines, int lowestHoriz
var line = new SvgPath()
{
Index = i,
- Data = $"M {ToS(HorizontalStartSpace)} {ToS(_boundHeight - AxisChartOptions.LabelExtraHeight - y)} L {ToS(_boundWidth - HorizontalEndSpace)} {ToS(_boundHeight - AxisChartOptions.LabelExtraHeight - y)}"
+ Data = $"M {ToS(HorizontalStartSpace)} {ToS(_boundHeight - y)} L {ToS(_boundWidth - HorizontalEndSpace)} {ToS(_boundHeight - y)}"
};
_horizontalLines.Add(line);
@@ -117,7 +117,7 @@ private void GenerateHorizontalGridLines(int numHorizontalLines, int lowestHoriz
var lineValue = new SvgText()
{
X = HorizontalStartSpace - 10,
- Y = _boundHeight - AxisChartOptions.LabelExtraHeight - y + 5,
+ Y = _boundHeight - y + 5,
Value = ToS(startGridY, MudChartParent?.ChartOptions.YAxisFormat)
};
_horizontalValues.Add(lineValue);
@@ -143,7 +143,7 @@ private void GenerateVerticalGridLines(int numVerticalLines, double gridXUnits,
var lineValue = new SvgText()
{
X = x,
- Y = _boundHeight - (AxisChartOptions.LabelExtraHeight / 2) - 10,
+ Y = _boundHeight - 10,
Value = xLabels
};
_verticalValues.Add(lineValue);
@@ -159,130 +159,138 @@ private void GenerateChartLines(int lowestHorizontalLine, double gridYUnits, dou
for (var i = 0; i < _series.Count; i++)
{
- var chartLine = new StringBuilder();
-
var series = _series[i];
- var data = series.Data;
- var chartDataCirlces = _chartDataPoints[i] = [];
- (double x, double y) GetXYForDataPoint(int index)
- {
- var x = HorizontalStartSpace + (index * horizontalSpace);
- var gridValue = ((data[index] / gridYUnits) - lowestHorizontalLine) * verticalSpace;
- var y = _boundHeight - VerticalStartSpace - AxisChartOptions.LabelExtraHeight - gridValue;
- return (x, y);
- }
- double GetYForZeroPoint()
+ if (series.Visible)
{
- var gridValue = (0 / gridYUnits - lowestHorizontalLine) * verticalSpace;
- var y = _boundHeight - VerticalStartSpace - AxisChartOptions.LabelExtraHeight - gridValue;
+ var chartLine = new StringBuilder();
+ var data = series.Data;
+ var chartDataCircles = _chartDataPoints[i] = [];
- return y;
- }
-
- var zeroPointY = GetYForZeroPoint();
- double firstPointX = 0;
- double firstPointY = 0;
- double lastPointX = 0;
-
- var interpolationEnabled = MudChartParent != null && MudChartParent.ChartOptions.InterpolationOption != InterpolationOption.Straight;
- if (interpolationEnabled)
- {
- var interpolationResolution = 10;
- var XValues = new double[data.Length];
- var YValues = new double[data.Length];
- for (var j = 0; j < data.Length; j++)
+ (double x, double y) GetXYForDataPoint(int index)
{
- var (x, y) = (XValues[j], YValues[j]) = GetXYForDataPoint(j);
-
- var dataValue = data[j];
- chartDataCirlces.Add(new()
- {
- Index = j,
- CX = x,
- CY = y,
- LabelX = x,
- LabelXValue = XAxisLabels[j / interpolationResolution],
- LabelY = y,
- LabelYValue = dataValue.ToString(),
- });
+ var x = HorizontalStartSpace + (index * horizontalSpace);
+ var gridValue = ((data[index] / gridYUnits) - lowestHorizontalLine) * verticalSpace;
+ var y = _boundHeight - VerticalStartSpace - gridValue;
+ return (x, y);
}
-
- ILineInterpolator interpolator = MudChartParent?.ChartOptions.InterpolationOption switch
+ double GetYForZeroPoint()
{
- InterpolationOption.NaturalSpline => new NaturalSpline(XValues, YValues, interpolationResolution),
- InterpolationOption.EndSlope => new EndSlopeSpline(XValues, YValues, interpolationResolution),
- InterpolationOption.Periodic => new PeriodicSpline(XValues, YValues, interpolationResolution),
- _ => throw new NotImplementedException("Interpolation option not implemented yet")
- };
+ var gridValue = (0 / gridYUnits - lowestHorizontalLine) * verticalSpace;
+ var y = _boundHeight - VerticalStartSpace - gridValue;
+
+ return y;
+ }
- var horizontalSpaceInterpolated = (_boundWidth - HorizontalStartSpace - HorizontalEndSpace) / (interpolator.InterpolatedXs.Length - 1);
+ var zeroPointY = GetYForZeroPoint();
+ double firstPointX = 0;
+ double firstPointY = 0;
+ double lastPointX = 0;
- for (var j = 0; j < interpolator.InterpolatedYs.Length; j++)
+ var interpolationEnabled = MudChartParent != null && MudChartParent.ChartOptions.InterpolationOption != InterpolationOption.Straight;
+ if (interpolationEnabled)
{
- var x = HorizontalStartSpace + (j * horizontalSpaceInterpolated);
- var y = interpolator.InterpolatedYs[j];
-
- if (j == 0)
+ var interpolationResolution = 10;
+ var XValues = new double[data.Length];
+ var YValues = new double[data.Length];
+ for (var j = 0; j < data.Length; j++)
{
- chartLine.Append("M ");
- firstPointX = x;
- firstPointY = y;
+ var (x, y) = (XValues[j], YValues[j]) = GetXYForDataPoint(j);
+
+ var dataValue = data[j];
+
+ if (MudChartParent?.ChartOptions.ShowToolTips != true)
+ {
+ continue;
+ }
+
+ chartDataCircles.Add(new()
+ {
+ Index = j,
+ CX = x,
+ CY = y,
+ LabelX = x,
+ LabelXValue = XAxisLabels[j / interpolationResolution],
+ LabelY = y,
+ LabelYValue = dataValue.ToString(series.DataMarkerTooltipYValueFormat),
+ });
}
- else
- chartLine.Append(" L ");
- if (j == interpolator.InterpolatedYs.Length - 1)
+ ILineInterpolator interpolator = MudChartParent?.ChartOptions.InterpolationOption switch
{
- lastPointX = x;
- }
+ InterpolationOption.NaturalSpline => new NaturalSpline(XValues, YValues, interpolationResolution),
+ InterpolationOption.EndSlope => new EndSlopeSpline(XValues, YValues, interpolationResolution),
+ InterpolationOption.Periodic => new PeriodicSpline(XValues, YValues, interpolationResolution),
+ _ => throw new NotImplementedException("Interpolation option not implemented yet")
+ };
- chartLine.Append(ToS(x));
- chartLine.Append(' ');
- chartLine.Append(ToS(y));
- }
- }
- else
- {
- for (var j = 0; j < data.Length; j++)
- {
- var (x, y) = GetXYForDataPoint(j);
+ var horizontalSpaceInterpolated = (_boundWidth - HorizontalStartSpace - HorizontalEndSpace) / (interpolator.InterpolatedXs.Length - 1);
- if (j == 0)
+ for (var j = 0; j < interpolator.InterpolatedYs.Length; j++)
{
- chartLine.Append("M ");
- firstPointX = x;
- firstPointY = y;
+ var x = HorizontalStartSpace + (j * horizontalSpaceInterpolated);
+ var y = interpolator.InterpolatedYs[j];
+
+ if (j == 0)
+ {
+ chartLine.Append("M ");
+ firstPointX = x;
+ firstPointY = y;
+ }
+ else
+ chartLine.Append(" L ");
+
+ if (j == interpolator.InterpolatedYs.Length - 1)
+ {
+ lastPointX = x;
+ }
+
+ chartLine.Append(ToS(x));
+ chartLine.Append(' ');
+ chartLine.Append(ToS(y));
}
- else
- chartLine.Append(" L ");
-
- if (j == data.Length - 1)
+ }
+ else
+ {
+ for (var j = 0; j < data.Length; j++)
{
- lastPointX = x;
+ var (x, y) = GetXYForDataPoint(j);
+
+ if (j == 0)
+ {
+ chartLine.Append("M ");
+ firstPointX = x;
+ firstPointY = y;
+ }
+ else
+ chartLine.Append(" L ");
+
+ if (j == data.Length - 1)
+ {
+ lastPointX = x;
+ }
+
+ chartLine.Append(ToS(x));
+ chartLine.Append(' ');
+ chartLine.Append(ToS(y));
+
+ var dataValue = data[j];
+
+ if (MudChartParent?.ChartOptions.ShowToolTips == true)
+ {
+ chartDataCircles.Add(new()
+ {
+ Index = j,
+ CX = x,
+ CY = y,
+ LabelX = x,
+ LabelXValue = XAxisLabels.Length > j ? XAxisLabels[j] : string.Empty,
+ LabelY = y,
+ LabelYValue = dataValue.ToString(series.DataMarkerTooltipYValueFormat),
+ });
+ }
}
-
- chartLine.Append(ToS(x));
- chartLine.Append(' ');
- chartLine.Append(ToS(y));
-
- var dataValue = data[j];
-
- chartDataCirlces.Add(new()
- {
- Index = j,
- CX = x,
- CY = y,
- LabelX = x,
- LabelXValue = XAxisLabels.Length > j ? XAxisLabels[j] : string.Empty,
- LabelY = y,
- LabelYValue = dataValue.ToString(),
- });
}
- }
-
- if (series.Visible)
- {
var line = new SvgPath()
{
Index = i,
@@ -324,6 +332,7 @@ double GetYForZeroPoint()
_chartAreas.Add(i, area);
}
}
+
var legend = new SvgLegend()
{
Index = i,
@@ -342,11 +351,10 @@ private void HandleLegendVisibilityChanged(SvgLegend legend)
RebuildChart();
}
- private void OnDataPointMouseOver(MouseEventArgs _, SvgCircle dataPoint)
+ private void OnDataPointMouseOver(MouseEventArgs _, SvgCircle dataPoint, SvgPath seriesPath)
{
_hoveredDataPoint = dataPoint;
- var seriesIndex = _chartDataPoints.First(x => x.Value.Contains(_hoveredDataPoint)).Key;
- _hoverDataPointChartLine = _chartLines[seriesIndex];
+ _hoverDataPointChartLine = seriesPath;
}
private void OnDataPointMouseOut(MouseEventArgs _)
diff --git a/src/MudBlazor/Components/Chart/Charts/Pie.razor b/src/MudBlazor/Components/Chart/Charts/Pie.razor
index 95a1ad3eddb6..b4ffab83c8c5 100644
--- a/src/MudBlazor/Components/Chart/Charts/Pie.razor
+++ b/src/MudBlazor/Components/Chart/Charts/Pie.razor
@@ -6,9 +6,9 @@
var chartClass = CircleDonutRatio < 1 ? "mud-chart-donut" : "mud-chart-pie";
}
-
+
-
+
@foreach (var item in _paths)
{
SelectedIndex = item.Index"
@@ -24,18 +24,18 @@
@MudChartParent?.CustomGraphics
@* Render the tooltip as an SVG group when a bar is hovered *@
- @if (_hoveredSegment is not null)
+ @if (_hoveredSegment is not null && MudChartParent?.ChartOptions.ShowToolTips == true)
{
- var color = MudChartParent?.ChartOptions.ChartPalette.GetValue(_hoveredSegment.Index % MudChartParent.ChartOptions.ChartPalette.Length)?.ToString() ?? string.Empty;
+ var color = MudChartParent.ChartOptions.ChartPalette.GetValue(_hoveredSegment.Index % MudChartParent.ChartOptions.ChartPalette.Length)?.ToString() ?? string.Empty;
- if (!string.IsNullOrWhiteSpace(MudChartParent?.ChartOptions.DefaultDataMarkerTooltipTitleFormat))
+ if (!string.IsNullOrWhiteSpace(MudChartParent.ChartOptions.DefaultDataMarkerTooltipTitleFormat))
{
var tooltipTitle = MudChartParent.ChartOptions.DefaultDataMarkerTooltipTitleFormat
- .Replace("{{SERIES_NAME}}", _hoveredSegment.LabelYValue)
- .Replace("{{X_VALUE}}", _hoveredSegment.LabelXValue)
- .Replace("{{Y_VALUE}}", _hoveredSegment.LabelYValue);
+ .Replace("{{SERIES_NAME}}", _hoveredSegment.LabelYValue)
+ .Replace("{{X_VALUE}}", _hoveredSegment.LabelXValue)
+ .Replace("{{Y_VALUE}}", _hoveredSegment.LabelYValue);
-
+
}
}
diff --git a/src/MudBlazor/Components/Chart/Charts/Pie.razor.cs b/src/MudBlazor/Components/Chart/Charts/Pie.razor.cs
index e2fa0aa4825b..e9f23b705ea0 100644
--- a/src/MudBlazor/Components/Chart/Charts/Pie.razor.cs
+++ b/src/MudBlazor/Components/Chart/Charts/Pie.razor.cs
@@ -1,5 +1,5 @@
-using System.Diagnostics.Metrics;
-using System.Globalization;
+using System.Globalization;
+using System.Text;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using MudBlazor.Extensions;
@@ -35,8 +35,8 @@ partial class Pie : MudCategoryChartBase
[Category(CategoryTypes.Chart.Appearance)]
public double CircleDonutRatio { get; set; } = 1;
- private List _paths = [];
- private List _legends = [];
+ private readonly List _paths = [];
+ private readonly List _legends = [];
private SvgPath? _hoveredSegment;
protected override void OnParametersSet()
@@ -60,12 +60,16 @@ protected override void OnParametersSet()
var data = normalizedData[i];
var startx = Math.Cos(cumulativeRadians);
var starty = Math.Sin(cumulativeRadians);
- cumulativeRadians += 2 * Math.PI * data;
+ cumulativeRadians += 2 * Math.PI * data / 2;
+ var midx = Math.Cos(cumulativeRadians);
+ var midy = Math.Sin(cumulativeRadians);
+ cumulativeRadians += 2 * Math.PI * data / 2;
var endx = Math.Cos(cumulativeRadians);
var endy = Math.Sin(cumulativeRadians);
var largeArcFlag = data > 0.5 ? 1 : 0;
SvgPath path;
+ var pathStringBuilder = new StringBuilder();
if (donutRadiusRatio < 1)
{
// Calculate inner radius with a hole.
@@ -74,32 +78,55 @@ protected override void OnParametersSet()
// Outer coordinates
var outerStartX = startx * Radius;
var outerStartY = starty * Radius;
+ var outerMidX = midx * Radius;
+ var outerMidY = midy * Radius;
var outerEndX = endx * Radius;
var outerEndY = endy * Radius;
// Inner coordinates (for the hole)
var innerStartX = startx * innerRadius;
var innerStartY = starty * innerRadius;
+ var innerMidX = midx * innerRadius;
+ var innerMidY = midy * innerRadius;
var innerEndX = endx * innerRadius;
var innerEndY = endy * innerRadius;
+
+ pathStringBuilder.Append($"M {ToS(outerStartX)} {ToS(outerStartY)} "); // Move to the start point
+ if (data >= 1)
+ {
+ pathStringBuilder.Append($"A {ToS(Radius)} {ToS(Radius)} 0 {ToS(largeArcFlag)} 1 {ToS(outerMidX)} {ToS(outerMidY)} "); // Add an arc to a mid point half way through the slice (outer) to support 100% donuts
+ }
+ pathStringBuilder.Append($"A {ToS(Radius)} {ToS(Radius)} 0 {ToS(largeArcFlag)} 1 {ToS(outerEndX)} {ToS(outerEndY)} "); // Add an arc to the end point (outer)
+ pathStringBuilder.Append($"L {ToS(innerEndX)} {ToS(innerEndY)} "); // Line to the end point of the inner arc
+ if (data >= 1)
+ {
+ pathStringBuilder.Append($"A {ToS(innerRadius)} {ToS(innerRadius)} 0 {ToS(largeArcFlag)} 0 {ToS(innerMidX)} {ToS(innerMidY)} "); // Add an arc to a mid point half way through the slice to support 100% donuts
+ }
+ pathStringBuilder.Append($"A {ToS(innerRadius)} {ToS(innerRadius)} 0 {ToS(largeArcFlag)} 0 {ToS(innerStartX)} {ToS(innerStartY)} Z"); // Add an arc to the start point (inner)
+
// Build a compound path: outer arc -> line to inner arc -> inner arc -> close
path = new SvgPath
{
Index = i,
- Data = $"M {ToS(outerStartX)} {ToS(outerStartY)} " +
- $"A {ToS(Radius)} {ToS(Radius)} 0 {ToS(largeArcFlag)} 1 {ToS(outerEndX)} {ToS(outerEndY)} " +
- $"L {ToS(innerEndX)} {ToS(innerEndY)} " +
- $"A {ToS(innerRadius)} {ToS(innerRadius)} 0 {ToS(largeArcFlag)} 0 {ToS(innerStartX)} {ToS(innerStartY)} Z"
+ Data = pathStringBuilder.ToString()
};
}
else
{
+ pathStringBuilder.Append($"M {ToS(startx * Radius)} {ToS(starty * Radius)} "); // Move to the start point
+ if (data >= 1)
+ {
+ pathStringBuilder.Append($"A {ToS(Radius)} {ToS(Radius)} 0 {ToS(largeArcFlag)} 1 {ToS(midx * Radius)} {ToS(midy * Radius)} "); // Add an arc to a mid point half way through the slice to support 100% pies
+ }
+ pathStringBuilder.Append($"A {ToS(Radius)} {ToS(Radius)} 0 {ToS(largeArcFlag)} 1 {ToS(endx * Radius)} {ToS(endy * Radius)} "); // Add an arc to the end point
+ pathStringBuilder.Append("L 0 0 Z"); // Line to the center
+
// Standard pie slice path going to the center.
path = new SvgPath()
{
Index = i,
- Data = $"M {ToS(startx * Radius)} {ToS(starty * Radius)} A {Radius} {Radius} 0 {ToS(largeArcFlag)} 1 {ToS(endx * Radius)} {ToS(endy * Radius)} L 0 0"
+ Data = pathStringBuilder.ToString()
};
}
@@ -107,9 +134,15 @@ protected override void OnParametersSet()
var midAngle = cumulativeRadians - Math.PI * data;
var midRadius = Radius * (1 - donutRadiusRatio / 2);
- // Calculate the midpoint coordinates at half the radius
- var midX = Math.Cos(midAngle) * midRadius;
- var midY = Math.Sin(midAngle) * midRadius;
+ var midX = 0d;
+ var midY = 0d;
+
+ if (donutRadiusRatio < 1 || data < 1) // don't find mid point when donut is 100% and data is 100%, just use the 0,0 point.
+ {
+ // Calculate the midpoint coordinates at half the radius
+ midX = Math.Cos(midAngle) * midRadius;
+ midY = Math.Sin(midAngle) * midRadius;
+ }
path.LabelX = midX;
path.LabelY = midY;
@@ -123,6 +156,10 @@ protected override void OnParametersSet()
{
var percent = normalizedData[i] * 100;
var labels = i < InputLabels.Length ? InputLabels[i] : "";
+
+ if (labels.Length == 0)
+ continue;
+
var legend = new SvgLegend()
{
Index = i,
diff --git a/src/MudBlazor/Components/Chart/Charts/StackedBar.razor b/src/MudBlazor/Components/Chart/Charts/StackedBar.razor
index 512c03282da9..2492b6d21110 100644
--- a/src/MudBlazor/Components/Chart/Charts/StackedBar.razor
+++ b/src/MudBlazor/Components/Chart/Charts/StackedBar.razor
@@ -1,6 +1,5 @@
@namespace MudBlazor.Charts
@using System.Globalization;
-@using Microsoft.JSInterop
@inherits MudCategoryAxisChartBase
@{
@@ -26,19 +25,19 @@
}
-
+
@foreach (var horizontalLineValue in _horizontalValues)
{
- @((MarkupString)$"{horizontalLineValue.Value?.ToString(CultureInfo.InvariantCulture)} ")
+ @((MarkupString)$"{horizontalLineValue.Value?.ToString(CultureInfo.InvariantCulture)} ")
}
-
+
@for (var i = 0; i < _verticalValues.Count; i++)
{
var verticalLineValue = _verticalValues[i];
- var x = verticalLineValue.X.ToString(CultureInfo.InvariantCulture);
- var y = verticalLineValue.Y.ToString(CultureInfo.InvariantCulture);
- var rotation = (-AxisChartOptions.LabelRotation).ToString(CultureInfo.InvariantCulture);
+ var x = ToS(verticalLineValue.X);
+ var y = ToS(verticalLineValue.Y);
+ var rotation = ToS(-AxisChartOptions.XAxisLabelRotation);
@((MarkupString)$"{verticalLineValue.Value?.ToString(CultureInfo.InvariantCulture)} ")
}
@@ -63,9 +62,9 @@
@MudChartParent?.CustomGraphics
@* Render the tooltip as an SVG group when a bar is hovered *@
- @if (_hoveredBar is not null)
+ @if (_hoveredBar is not null && MudChartParent?.ChartOptions.ShowToolTips == true)
{
- var color = MudChartParent?.ChartOptions.ChartPalette.GetValue(_hoveredBar.Index % MudChartParent.ChartOptions.ChartPalette.Length)?.ToString() ?? string.Empty;
+ var color = MudChartParent.ChartOptions.ChartPalette.GetValue(_hoveredBar.Index % MudChartParent.ChartOptions.ChartPalette.Length)?.ToString() ?? string.Empty;
var series = _series[_hoveredBar.Index];
if (!string.IsNullOrWhiteSpace(series.DataMarkerTooltipTitleFormat))
diff --git a/src/MudBlazor/Components/Chart/Charts/StackedBar.razor.cs b/src/MudBlazor/Components/Chart/Charts/StackedBar.razor.cs
index 39b3a580e3ec..3ee2840e4ad8 100644
--- a/src/MudBlazor/Components/Chart/Charts/StackedBar.razor.cs
+++ b/src/MudBlazor/Components/Chart/Charts/StackedBar.razor.cs
@@ -1,5 +1,4 @@
-using Microsoft.AspNetCore.Components;
-using Microsoft.AspNetCore.Components.Web;
+using Microsoft.AspNetCore.Components.Web;
using MudBlazor.Extensions;
#nullable enable
@@ -17,16 +16,16 @@ partial class StackedBar : MudCategoryAxisChartBase
{
private const double BarOverlapAmountFix = 0.5; // used to trigger slight overlap so the bars don't have gaps due to floating point rounding
- private List _horizontalLines = [];
- private List _horizontalValues = [];
+ private readonly List _horizontalLines = [];
+ private readonly List _horizontalValues = [];
- private List _verticalLines = [];
- private List _verticalValues = [];
+ private readonly List _verticalLines = [];
+ private readonly List _verticalValues = [];
- private List _legends = [];
+ private readonly List _legends = [];
private List _series = [];
- private List _bars = [];
+ private readonly List _bars = [];
private double _barWidth;
private double _barWidthStroke;
private SvgPath? _hoveredBar;
@@ -52,7 +51,7 @@ protected override void RebuildChart()
// Calculate spacing – note the horizontal space is computed so that the vertical grid lines line up
double horizontalSpace = Math.Round((_boundWidth - HorizontalStartSpace - HorizontalEndSpace) / (numVerticalLines > 1 ? (numVerticalLines) : 1), 1);
- double verticalSpace = (_boundHeight - VerticalStartSpace - VerticalEndSpace - AxisChartOptions.LabelExtraHeight) / (numHorizontalLines > 1 ? (numHorizontalLines) : 1);
+ double verticalSpace = (_boundHeight - VerticalStartSpace - VerticalEndSpace) / (numHorizontalLines > 1 ? (numHorizontalLines) : 1);
GenerateHorizontalGridLines(numHorizontalLines, gridYUnits, verticalSpace);
GenerateVerticalGridLines(numVerticalLines, horizontalSpace);
@@ -106,6 +105,16 @@ private void ComputeStackedUnitsAndNumberOfLines(
}
var maxY = stackedTotals.Any() ? stackedTotals.Max() : 0;
numHorizontalLines = (int)(maxY / gridYUnits) + 1;
+
+ // this is a safeguard against millions of gridlines which might arise with very high values
+ var maxYTicks = MudChartParent?.ChartOptions.MaxNumYAxisTicks ?? 20;
+ while (numHorizontalLines > maxYTicks)
+ {
+ gridYUnits *= 2;
+ var lowestHorizontalLine = Math.Min((int)Math.Floor(0 / gridYUnits), 0);
+ var highestHorizontalLine = Math.Max((int)Math.Ceiling(maxY / gridYUnits), 0);
+ numHorizontalLines = highestHorizontalLine - lowestHorizontalLine + 1;
+ }
}
///
@@ -124,14 +133,14 @@ private void GenerateHorizontalGridLines(int numHorizontalLines, double gridYUni
var line = new SvgPath()
{
Index = i,
- Data = $"M {ToS(HorizontalStartSpace)} {ToS(_boundHeight - AxisChartOptions.LabelExtraHeight - y)} L {ToS(_boundWidth - HorizontalEndSpace)} {ToS(_boundHeight - AxisChartOptions.LabelExtraHeight - y)}"
+ Data = $"M {ToS(HorizontalStartSpace)} {ToS(_boundHeight - y)} L {ToS(_boundWidth - HorizontalEndSpace)} {ToS(_boundHeight - y)}"
};
_horizontalLines.Add(line);
var text = new SvgText()
{
X = HorizontalStartSpace - 10,
- Y = _boundHeight - AxisChartOptions.LabelExtraHeight - y + 5,
+ Y = _boundHeight - y + 5,
Value = ToS(lineValue, MudChartParent?.ChartOptions.YAxisFormat)
};
_horizontalValues.Add(text);
@@ -163,7 +172,7 @@ private void GenerateVerticalGridLines(int numVerticalLines, double horizontalSp
var text = new SvgText()
{
X = x,
- Y = _boundHeight - (AxisChartOptions.LabelExtraHeight / 2) - 10,
+ Y = _boundHeight - XAxisLabelOffset,
Value = label,
};
_verticalValues.Add(text);
@@ -186,7 +195,7 @@ private void GenerateStackedBars(double gridYUnits, double horizontalSpace, doub
{
double x = HorizontalStartSpace + startPadding + (j * horizontalSpace);
- var yStart = _boundHeight - VerticalStartSpace - AxisChartOptions.LabelExtraHeight;
+ var yStart = _boundHeight - VerticalStartSpace;
for (int i = 0; i < _series.Count; i++)
{
var series = _series[i];
@@ -206,7 +215,7 @@ private void GenerateStackedBars(double gridYUnits, double horizontalSpace, doub
Index = i,
Data = $"M {ToS(x)} {ToS(yStart)} L {ToS(x)} {ToS(yEnd - BarOverlapAmountFix)}",
LabelXValue = XAxisLabels.Length > j ? XAxisLabels[j] : string.Empty,
- LabelYValue = dataValue.ToString(),
+ LabelYValue = dataValue.ToString(series.DataMarkerTooltipYValueFormat),
LabelX = x,
LabelY = yEnd
};
diff --git a/src/MudBlazor/Components/Chart/Charts/TimeSeries.razor b/src/MudBlazor/Components/Chart/Charts/TimeSeries.razor
index 63d7a69c9ecc..352f7e3761a2 100644
--- a/src/MudBlazor/Components/Chart/Charts/TimeSeries.razor
+++ b/src/MudBlazor/Components/Chart/Charts/TimeSeries.razor
@@ -30,7 +30,7 @@
}
-
+
@foreach (var horizontalLineValue in _horizontalValues)
{
@((MarkupString)$"{horizontalLineValue.Value?.ToString(CultureInfo.InvariantCulture)} ")
@@ -38,19 +38,17 @@
@if (YAxisTitle is not null)
{
-
- @YAxisTitle
-
+ @YAxisTitle
}
-
+
@for (var i = 0; i < _verticalValues.Count; i++)
{
var verticalLineValue = _verticalValues[i];
var x = verticalLineValue.X.ToString(CultureInfo.InvariantCulture);
var y = verticalLineValue.Y.ToString(CultureInfo.InvariantCulture);
- var rotation = (-AxisChartOptions.LabelRotation).ToString(CultureInfo.InvariantCulture);
+ var rotation = (-AxisChartOptions.XAxisLabelRotation).ToString(CultureInfo.InvariantCulture);
@((MarkupString)$"{verticalLineValue.Value?.ToString(CultureInfo.InvariantCulture)} ")
}
@@ -89,7 +87,7 @@
cx="@ToS(item.CX)"
cy="@ToS(item.CY)"
r="@ToS(dataPointHoverRadius)"
- @onmouseover="(e) => OnDataPointMouseOver(e, item)"
+ @onmouseover="(e) => OnDataPointMouseOver(e, item, chartLine)"
@onmouseout="OnDataPointMouseOut">
}
@@ -99,11 +97,11 @@
@MudChartParent?.CustomGraphics
@* Render the tooltip as an SVG group when a bar is hovered *@
- @if (_hoveredDataPoint is not null && _hoverDataPointChartLine is not null)
+ @if (_hoveredDataPoint is not null && _hoverDataPointChartLine is not null && MudChartParent?.ChartOptions.ShowToolTips == true)
{
var seriesIndex = _chartDataPoints.First(x => x.Value.Contains(_hoveredDataPoint)).Key;
- var color = MudChartParent?.ChartOptions.ChartPalette.GetValue(seriesIndex % MudChartParent.ChartOptions.ChartPalette.Length)?.ToString() ?? string.Empty;
+ var color = MudChartParent.ChartOptions.ChartPalette.GetValue(seriesIndex % MudChartParent.ChartOptions.ChartPalette.Length)?.ToString() ?? string.Empty;
var series = _series[seriesIndex];
if (!string.IsNullOrWhiteSpace(series.DataMarkerTooltipTitleFormat))
diff --git a/src/MudBlazor/Components/Chart/Charts/TimeSeries.razor.cs b/src/MudBlazor/Components/Chart/Charts/TimeSeries.razor.cs
index e8fbb44f0c38..677477b84bf2 100644
--- a/src/MudBlazor/Components/Chart/Charts/TimeSeries.razor.cs
+++ b/src/MudBlazor/Components/Chart/Charts/TimeSeries.razor.cs
@@ -12,23 +12,24 @@ namespace MudBlazor.Charts
///
/// A chart which displays values over time.
///
- ///
- ///
- ///
- ///
- ///
partial class TimeSeries : MudTimeSeriesChartBase, IDisposable
{
private const double Epsilon = 1e-6;
private const double BoundWidthDefault = 800;
private const double BoundHeightDefault = 350;
- private const double HorizontalStartSpace = 80.0; // needs space to have the full label visible and be even to the end space
- private const double HorizontalEndSpace = 80.0; // needs space to have the full label visible and be even to the start space
- private const double VerticalStartSpace = 25.0;
+ private const double HorizontalStartSpaceBuffer = 10.0;
+ protected double HorizontalStartSpace => Math.Max(HorizontalStartSpaceBuffer + (_yAxisLabelSize?.Width ?? 0), 30);
+ private const double HorizontalEndSpace = 30.0;
+ private const double VerticalStartSpaceBuffer = 10.0;
+ protected double VerticalStartSpace => Math.Max(VerticalStartSpaceBuffer + (_xAxisLabelSize?.Height ?? 0), 30);
private const double VerticalEndSpace = 25.0;
+ protected double XAxisLabelOffset => Math.Ceiling(_xAxisLabelSize?.Height ?? 20) / 2;
+
private double _boundWidth = BoundWidthDefault;
private double _boundHeight = BoundHeightDefault;
private ElementSize? _elementSize = null;
+ private ElementSize? _yAxisLabelSize;
+ private ElementSize? _xAxisLabelSize;
[Inject]
private IJSRuntime JsRuntime { get; set; } = null!;
@@ -36,26 +37,28 @@ partial class TimeSeries : MudTimeSeriesChartBase, IDisposable
[CascadingParameter]
public MudTimeSeriesChartBase? MudChartParent { get; set; }
- private List _horizontalLines = [];
- private List _horizontalValues = [];
+ private readonly List _horizontalLines = [];
+ private readonly List _horizontalValues = [];
- private List _verticalLines = [];
- private List _verticalValues = [];
+ private readonly List _verticalLines = [];
+ private readonly List _verticalValues = [];
- private List _legends = [];
+ private readonly List _legends = [];
private List _series = [];
- private List _chartLines = [];
- private Dictionary _chartAreas = [];
- private Dictionary> _chartDataPoints = [];
+ private readonly List _chartLines = [];
+ private readonly Dictionary _chartAreas = [];
+ private readonly Dictionary> _chartDataPoints = [];
private SvgCircle? _hoveredDataPoint;
private SvgPath? _hoverDataPointChartLine;
private DateTime _minDateTime;
private DateTime _maxDateTime;
private TimeSpan _minDateLabelOffset;
- private DotNetObjectReference _dotNetObjectReference;
+ private readonly DotNetObjectReference _dotNetObjectReference;
private ElementReference _elementReference;
+ protected ElementReference? _xAxisGroupElementReference;
+ protected ElementReference? _yAxisGroupElementReference;
public TimeSeries()
{
@@ -75,9 +78,32 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
if (firstRender)
{
- _elementSize = await JsRuntime.InvokeAsync("mudObserveElementSize", _dotNetObjectReference, _elementReference);
+ var elementSize = await JsRuntime.InvokeAsync("mudObserveElementSize", _dotNetObjectReference, _elementReference);
+
+ OnElementSizeChanged(elementSize);
+ }
- OnElementSizeChanged(_elementSize);
+ var yAxisLabelSize = _yAxisGroupElementReference != null ? await JsRuntime.InvokeAsync("mudGetSvgBBox", _yAxisGroupElementReference) : null;
+ var xAxisLabelSize = _xAxisGroupElementReference != null ? await JsRuntime.InvokeAsync("mudGetSvgBBox", _xAxisGroupElementReference) : null;
+
+ var axisChanged = false;
+ if (yAxisLabelSize != null && (_yAxisLabelSize == null || !DoubleEpsilonEqualityComparer.Default.Equals(yAxisLabelSize.Height, _yAxisLabelSize.Height)))
+ {
+ _yAxisLabelSize = yAxisLabelSize;
+ axisChanged = true;
+ }
+
+ if (xAxisLabelSize != null && (_xAxisLabelSize == null || !DoubleEpsilonEqualityComparer.Default.Equals(xAxisLabelSize.Width, _xAxisLabelSize.Width)))
+ {
+ _xAxisLabelSize = xAxisLabelSize;
+ axisChanged = true;
+ }
+
+ // maybe there should be some kind of cancellation token here to prevent multiple rebuilds when the invokeasync takes time in server mode and subsequent renders have started to take place
+ if (axisChanged)
+ {
+ RebuildChart();
+ StateHasChanged();
}
}
@@ -90,8 +116,8 @@ private void RebuildChart()
ComputeMinAndMaxDateTimes();
ComputeUnitsAndNumberOfLines(out var gridXUnits, out var gridYUnits, out var numHorizontalLines, out var lowestHorizontalLine, out var numVerticalLines);
- var horizontalSpace = (_boundWidth - HorizontalStartSpace - HorizontalEndSpace) / Math.Max(1, numVerticalLines - 1);
- var verticalSpace = (_boundHeight - VerticalStartSpace - VerticalEndSpace - AxisChartOptions.LabelExtraHeight) / Math.Max(1, numHorizontalLines - 1);
+ var horizontalSpace = (_boundWidth - HorizontalStartSpace - HorizontalEndSpace) / Math.Max(1, (_maxDateTime - _minDateTime) / TimeLabelSpacing);
+ var verticalSpace = (_boundHeight - VerticalStartSpace - VerticalEndSpace) / Math.Max(1, numHorizontalLines - 1);
GenerateHorizontalGridLines(numHorizontalLines, lowestHorizontalLine, gridYUnits, verticalSpace);
GenerateVerticalGridLines(numVerticalLines, gridXUnits, horizontalSpace);
@@ -122,9 +148,9 @@ private void SetBounds()
}
[JSInvokable]
- public void OnElementSizeChanged(ElementSize elementSize)
+ public void OnElementSizeChanged(ElementSize? elementSize)
{
- if (elementSize == null)
+ if (elementSize == null || elementSize.Timestamp <= _elementSize?.Timestamp)
return;
_elementSize = elementSize;
@@ -215,7 +241,7 @@ private void ComputeUnitsAndNumberOfLines(out double gridXUnits, out double grid
var labelSpacing = TimeLabelSpacing;
- numVerticalLines = (int)Math.Ceiling((_maxDateTime - _minDateTime) / labelSpacing);
+ numVerticalLines = (int)((_maxDateTime - _minDateTime) / labelSpacing) + 1;
}
else
{
@@ -236,7 +262,7 @@ private void GenerateHorizontalGridLines(int numHorizontalLines, int lowestHoriz
var line = new SvgPath()
{
Index = i,
- Data = $"M {ToS(HorizontalStartSpace)} {ToS((_boundHeight - AxisChartOptions.LabelExtraHeight - y))} L {ToS((_boundWidth - HorizontalEndSpace))} {ToS((_boundHeight - AxisChartOptions.LabelExtraHeight - y))}"
+ Data = $"M {ToS(HorizontalStartSpace)} {ToS(_boundHeight - y)} L {ToS(_boundWidth - HorizontalEndSpace)} {ToS(_boundHeight - y)}"
};
_horizontalLines.Add(line);
@@ -244,7 +270,7 @@ private void GenerateHorizontalGridLines(int numHorizontalLines, int lowestHoriz
var lineValue = new SvgText()
{
X = HorizontalStartSpace - 10,
- Y = _boundHeight - AxisChartOptions.LabelExtraHeight - y + 5,
+ Y = _boundHeight - y + 5,
Value = ToS(startGridY, MudChartParent?.ChartOptions.YAxisFormat)
};
_horizontalValues.Add(lineValue);
@@ -280,7 +306,7 @@ private void GenerateVerticalGridLines(int numVerticalLines, double gridXUnits,
var line = new SvgPath()
{
Index = i,
- Data = $"M {ToS(x)} {ToS((_boundHeight - VerticalStartSpace))} L {ToS(x)} {ToS(VerticalEndSpace)}"
+ Data = $"M {ToS(x)} {ToS(_boundHeight - VerticalStartSpace)} L {ToS(x)} {ToS(VerticalEndSpace)}"
};
_verticalLines.Add(line);
@@ -289,7 +315,7 @@ private void GenerateVerticalGridLines(int numVerticalLines, double gridXUnits,
var lineValue = new SvgText()
{
X = x,
- Y = _boundHeight - (AxisChartOptions.LabelExtraHeight / 2) - 10,
+ Y = _boundHeight - XAxisLabelOffset,
Value = xLabels.ToString(TimeLabelFormat),
};
_verticalValues.Add(lineValue);
@@ -310,83 +336,86 @@ private void GenerateChartLines(int lowestHorizontalLine, double gridYUnits, dou
for (var i = 0; i < _series.Count; i++)
{
- var chartLine = new StringBuilder();
-
var series = _series[i];
- var data = series.Data;
- var chartDataCirlces = _chartDataPoints[i] = [];
-
- if (data.Count <= 0)
- continue;
- (double x, double y) GetXYForDataPoint(int index)
+ if (series.IsVisible)
{
- var dateTime = data[index].DateTime;
+ var chartLine = new StringBuilder();
+ var data = series.Data;
+ var chartDataCirlces = _chartDataPoints[i] = [];
- var diffFromMin = dateTime - _minDateTime;
+ if (data.Count <= 0)
+ continue;
- var gridValue = (data[index].Value / gridYUnits - lowestHorizontalLine) * verticalSpace;
- var y = _boundHeight - VerticalStartSpace - AxisChartOptions.LabelExtraHeight - gridValue;
+ (double x, double y) GetXYForDataPoint(int index)
+ {
+ var dateTime = data[index].DateTime;
- if (fullDateTimeDiff.TotalMilliseconds == 0)
- return (HorizontalStartSpace, y);
+ var diffFromMin = dateTime - _minDateTime;
- var x = HorizontalStartSpace + diffFromMin.TotalMilliseconds / fullDateTimeDiff.TotalMilliseconds * (_boundWidth - HorizontalStartSpace - HorizontalEndSpace);
+ var gridValue = (data[index].Value / gridYUnits - lowestHorizontalLine) * verticalSpace;
+ var y = _boundHeight - VerticalStartSpace - gridValue;
- return (x, y);
- }
- double GetYForZeroPoint()
- {
- var gridValue = (0 / gridYUnits - lowestHorizontalLine) * verticalSpace;
- var y = _boundHeight - VerticalStartSpace - AxisChartOptions.LabelExtraHeight - gridValue;
+ if (fullDateTimeDiff.TotalMilliseconds == 0)
+ return (HorizontalStartSpace, y);
- return y;
- }
+ var x = HorizontalStartSpace + (diffFromMin.TotalMilliseconds / fullDateTimeDiff.TotalMilliseconds * (_boundWidth - HorizontalStartSpace - HorizontalEndSpace));
- bool interpolationEnabled = MudChartParent != null && MudChartParent.ChartOptions.InterpolationOption != InterpolationOption.Straight;
- if (interpolationEnabled)
- {
- // TODO this is not simple to implement, as the x values are not linearly spaced
- // and the interpolation should be done based on the datetime
- // so we need to find a way to interpolate the x values based on the datetime
- // and then interpolate the y values based on the x values
- // this is not trivial and needs to be done in a separate PR
-
- throw new NotImplementedException("Interpolation not implemented yet for timeseries charts");
- }
- else
- {
- for (var j = 0; j < data.Count; j++)
+ return (x, y);
+ }
+ double GetYForZeroPoint()
{
- var (x, y) = GetXYForDataPoint(j);
-
- if (j == 0)
- {
- chartLine.Append("M ");
- }
- else
- chartLine.Append(" L ");
+ var gridValue = (0 / gridYUnits - lowestHorizontalLine) * verticalSpace;
+ var y = _boundHeight - VerticalStartSpace - gridValue;
- chartLine.Append(ToS(x));
- chartLine.Append(' ');
- chartLine.Append(ToS(y));
+ return y;
+ }
- var dataValue = data[j];
+ bool interpolationEnabled = MudChartParent != null && MudChartParent.ChartOptions.InterpolationOption != InterpolationOption.Straight;
+ if (interpolationEnabled)
+ {
+ // TODO this is not simple to implement, as the x values are not linearly spaced
+ // and the interpolation should be done based on the datetime
+ // so we need to find a way to interpolate the x values based on the datetime
+ // and then interpolate the y values based on the x values
+ // this is not trivial and needs to be done in a separate PR
- chartDataCirlces.Add(new()
+ throw new NotImplementedException("Interpolation not implemented yet for timeseries charts");
+ }
+ else
+ {
+ for (var j = 0; j < data.Count; j++)
{
- Index = j,
- CX = x,
- CY = y,
- LabelX = x,
- LabelXValue = dataValue.DateTime.ToString(MudChartParent?.DataMarkerTooltipTimeLabelFormat ?? "{0}"),
- LabelY = y,
- LabelYValue = dataValue.Value.ToString(),
- });
+ var (x, y) = GetXYForDataPoint(j);
+
+ if (j == 0)
+ {
+ chartLine.Append("M ");
+ }
+ else
+ chartLine.Append(" L ");
+
+ chartLine.Append(ToS(x));
+ chartLine.Append(' ');
+ chartLine.Append(ToS(y));
+
+ var dataValue = data[j];
+
+ if (MudChartParent?.ChartOptions.ShowToolTips != true)
+ continue;
+
+ chartDataCirlces.Add(new()
+ {
+ Index = j,
+ CX = x,
+ CY = y,
+ LabelX = x,
+ LabelXValue = dataValue.DateTime.ToString(MudChartParent?.DataMarkerTooltipTimeLabelFormat ?? "{0}"),
+ LabelY = y,
+ LabelYValue = dataValue.Value.ToString(series.DataMarkerTooltipYValueFormat),
+ });
+ }
}
- }
- if (_series[i].IsVisible)
- {
var line = new SvgPath()
{
Index = i,
@@ -436,8 +465,8 @@ double GetYForZeroPoint()
var legend = new SvgLegend()
{
Index = i,
- Labels = _series[i].Name,
- Visible = _series[i].IsVisible,
+ Labels = series.Name,
+ Visible = series.IsVisible,
OnVisibilityChanged = EventCallback.Factory.Create(this, HandleLegendVisibilityChanged)
};
_legends.Add(legend);
@@ -451,11 +480,10 @@ private void HandleLegendVisibilityChanged(SvgLegend legend)
RebuildChart();
}
- private void OnDataPointMouseOver(MouseEventArgs _, SvgCircle dataPoint)
+ private void OnDataPointMouseOver(MouseEventArgs _, SvgCircle dataPoint, SvgPath seriesPath)
{
_hoveredDataPoint = dataPoint;
- var seriesIndex = _chartDataPoints.First(x => x.Value.Contains(_hoveredDataPoint)).Key;
- _hoverDataPointChartLine = _chartLines[seriesIndex];
+ _hoverDataPointChartLine = seriesPath;
}
private void OnDataPointMouseOut(MouseEventArgs _)
diff --git a/src/MudBlazor/Components/Chart/Models/AxisChartOptions.cs b/src/MudBlazor/Components/Chart/Models/AxisChartOptions.cs
index f511d12ae06a..b96c9ec5e089 100644
--- a/src/MudBlazor/Components/Chart/Models/AxisChartOptions.cs
+++ b/src/MudBlazor/Components/Chart/Models/AxisChartOptions.cs
@@ -11,11 +11,18 @@ public class AxisChartOptions
///
/// Rotation angle to rotate the labels in degrees.
///
- public int LabelRotation { get; set; }
+ [Obsolete("Renamed to XAxisLabelRotation. This will be removed in a future major version.", false)]
+ public int LabelRotation { get => XAxisLabelRotation; set => XAxisLabelRotation = value; }
///
- /// Extra height to fit rotated labels.
+ /// Rotation angle to rotate the labels in degrees.
+ ///
+ public int XAxisLabelRotation { get; set; }
+
+ ///
+ /// Extra height to fit XAxis rotated labels.
///
+ [Obsolete("No longer required, labels are now calculated automatically. This will be removed in a future major version.", false)]
public int LabelExtraHeight { get; set; }
///
diff --git a/src/MudBlazor/Components/Chart/Models/ChartOptions.cs b/src/MudBlazor/Components/Chart/Models/ChartOptions.cs
index 40e0d93acad9..5efea1147a47 100644
--- a/src/MudBlazor/Components/Chart/Models/ChartOptions.cs
+++ b/src/MudBlazor/Components/Chart/Models/ChartOptions.cs
@@ -120,7 +120,7 @@ public class ChartOptions
public YAxisLabelPosition YAxisLabelPosition { get; set; } = YAxisLabelPosition.Left;
///
- /// Enables tooltips for values in a
+ /// Enables tooltips for values
/// Defaults to true
///
public bool ShowToolTips { get; set; } = true;
diff --git a/src/MudBlazor/Components/Chart/Models/ChartSeries.cs b/src/MudBlazor/Components/Chart/Models/ChartSeries.cs
index ce0c04876856..84be0a71a295 100644
--- a/src/MudBlazor/Components/Chart/Models/ChartSeries.cs
+++ b/src/MudBlazor/Components/Chart/Models/ChartSeries.cs
@@ -50,6 +50,11 @@ public class ChartSeries
///
public string? DataMarkerTooltipSubtitleFormat { get; set; }
+ ///
+ /// Tooltip YValue format for the series. It is used to format the {{Y_VALUE}} tag.
+ ///
+ public string? DataMarkerTooltipYValueFormat { get; set; }
+
public LineDisplayType LineDisplayType { get; set; }
public double FillOpacity { get; set; } = 0.4;
diff --git a/src/MudBlazor/Components/Chart/Models/TimeSeriesChartSeries.cs b/src/MudBlazor/Components/Chart/Models/TimeSeriesChartSeries.cs
index e60e63459dbe..1da1bd5f1b02 100644
--- a/src/MudBlazor/Components/Chart/Models/TimeSeriesChartSeries.cs
+++ b/src/MudBlazor/Components/Chart/Models/TimeSeriesChartSeries.cs
@@ -35,5 +35,10 @@ public record TimeValue(DateTime DateTime, double Value);
/// Tooltip subtitle format for the series. Supported tags are {{SERIES_NAME}}, {{X_VALUE}} and {{Y_VALUE}}.
///
public string? DataMarkerTooltipSubtitleFormat { get; set; }
+
+ ///
+ /// Tooltip YValue format for the series. It is used to format the {{Y_VALUE}} tag.
+ ///
+ public string? DataMarkerTooltipYValueFormat { get; set; }
}
}
diff --git a/src/MudBlazor/Components/Chart/MudCategoryAxisChartBase.cs b/src/MudBlazor/Components/Chart/MudCategoryAxisChartBase.cs
index b1c94bbad398..c0afacf1fcc9 100644
--- a/src/MudBlazor/Components/Chart/MudCategoryAxisChartBase.cs
+++ b/src/MudBlazor/Components/Chart/MudCategoryAxisChartBase.cs
@@ -18,19 +18,26 @@ public abstract class MudCategoryAxisChartBase : MudCategoryChartBase, IDisposab
public MudChart? MudChartParent { get; set; }
private const double Epsilon = 1e-6;
- protected const double HorizontalStartSpace = 30.0;
+ protected const double BoundWidthDefault = 650.0;
+ protected const double BoundHeightDefault = 350.0;
+ protected const double HorizontalStartSpaceBuffer = 10.0;
+ protected double HorizontalStartSpace => Math.Max(HorizontalStartSpaceBuffer + Math.Ceiling(_yAxisLabelSize?.Width ?? 0), 30);
protected const double HorizontalEndSpace = 30.0;
- protected const double VerticalStartSpace = 25.0;
+ protected const double VerticalStartSpaceBuffer = 10.0;
+ protected double VerticalStartSpace => Math.Max(VerticalStartSpaceBuffer + (_xAxisLabelSize?.Height ?? 0), 30);
protected const double VerticalEndSpace = 25.0;
+ protected double XAxisLabelOffset => Math.Ceiling(_xAxisLabelSize?.Height ?? 20) / 2;
- protected const double BoundWidthDefault = 650.0;
- protected const double BoundHeightDefault = 350.0;
protected double _boundWidth = 650.0;
protected double _boundHeight = 350.0;
- private ElementSize _elementSize = new() { Width = BoundWidthDefault, Height = BoundHeightDefault };
+ private ElementSize? _elementSize;
+ private ElementSize? _yAxisLabelSize;
+ private ElementSize? _xAxisLabelSize;
private readonly DotNetObjectReference _dotNetObjectReference;
- protected ElementReference _elementReference = new();
+ protected ElementReference _elementReference;
+ protected ElementReference? _xAxisGroupElementReference;
+ protected ElementReference? _yAxisGroupElementReference;
[DynamicDependency(nameof(OnElementSizeChanged))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(ElementSize))]
@@ -45,29 +52,65 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
if (firstRender)
{
- _elementSize = await JsRuntime.InvokeAsync("mudObserveElementSize", _dotNetObjectReference, _elementReference);
+ var elementSize = await JsRuntime.InvokeAsync("mudObserveElementSize", _dotNetObjectReference, _elementReference);
+
+ OnElementSizeChanged(elementSize);
+ }
+
+ var yAxisLabelSize = _yAxisGroupElementReference != null ? await JsRuntime.InvokeAsync("mudGetSvgBBox", _yAxisGroupElementReference) : null;
+ var xAxisLabelSize = _xAxisGroupElementReference != null ? await JsRuntime.InvokeAsync("mudGetSvgBBox", _xAxisGroupElementReference) : null;
+
+ var axisChanged = false;
+ var comparer = new DoubleEpsilonEqualityComparer(0.01);
+ if (yAxisLabelSize != null && (_yAxisLabelSize == null || !comparer.Equals(yAxisLabelSize.Width, _yAxisLabelSize.Width)))
+ {
+ _yAxisLabelSize = yAxisLabelSize;
+ axisChanged = true;
+ }
+
+ if (xAxisLabelSize != null && (_xAxisLabelSize == null || !comparer.Equals(xAxisLabelSize.Height, _xAxisLabelSize.Height)))
+ {
+ _xAxisLabelSize = xAxisLabelSize;
+ axisChanged = true;
+ }
- OnElementSizeChanged(_elementSize);
+ // maybe there should be some kind of cancellation token here to prevent multiple rebuilds when the invokeasync takes time in server mode and subsequent renders have started to take place
+ if (axisChanged)
+ {
+ RebuildChart();
+ StateHasChanged();
}
}
- protected virtual void SetBounds()
+ protected void SetBounds()
{
_boundWidth = BoundWidthDefault;
_boundHeight = BoundHeightDefault;
-#pragma warning disable CS0618
- if (MudChartParent != null && AxisChartOptions.MatchBoundsToSize)
+ if (MudChartParent != null && (MudChartParent.AxisChartOptions.MatchBoundsToSize)) // backwards compatibilitly to the mudchartparent approach
{
- _boundWidth = _elementSize.Width;
- _boundHeight = _elementSize.Height;
+ if (_elementSize != null)
+ {
+ _boundWidth = _elementSize.Width;
+ _boundHeight = _elementSize.Height;
+ }
+ else if (MudChartParent.Width.EndsWith("px")
+ && MudChartParent.Height.EndsWith("px")
+ && double.TryParse(MudChartParent.Width.AsSpan(0, MudChartParent.Width.Length - 2), out var width)
+ && double.TryParse(MudChartParent.Height.AsSpan(0, MudChartParent.Height.Length - 2), out var height))
+ {
+ _boundWidth = width;
+ _boundHeight = height;
+ }
}
-#pragma warning restore CS0618
}
[JSInvokable]
public void OnElementSizeChanged(ElementSize elementSize)
{
+ if (elementSize == null || elementSize.Timestamp <= _elementSize?.Timestamp)
+ return;
+
_elementSize = elementSize;
if (!AxisChartOptions.MatchBoundsToSize)
diff --git a/src/MudBlazor/Components/Chart/MudChartBase.cs b/src/MudBlazor/Components/Chart/MudChartBase.cs
index a2399d500209..f9b8e778bcc9 100644
--- a/src/MudBlazor/Components/Chart/MudChartBase.cs
+++ b/src/MudBlazor/Components/Chart/MudChartBase.cs
@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System.Globalization;
-using System.Runtime.InteropServices.JavaScript;
using Microsoft.AspNetCore.Components;
using MudBlazor.Utilities;
@@ -133,14 +132,6 @@ internal void SetSelectedIndex(int index)
[Category(CategoryTypes.Chart.Behavior)]
public EventCallback SelectedIndexChanged { get; set; }
- protected string ToS(double d, string? format = null)
- {
- if (string.IsNullOrEmpty(format))
- return Math.Round(d, 4).ToString(CultureInfo.InvariantCulture);
-
- return Math.Round(d, 4).ToString(format);
- }
-
///
/// Allows series to be hidden when is .
///
diff --git a/src/MudBlazor/Components/Chart/Parts/ChartTooltip.razor b/src/MudBlazor/Components/Chart/Parts/ChartTooltip.razor
index cf9a0819d0be..cf2bccf50ce4 100644
--- a/src/MudBlazor/Components/Chart/Parts/ChartTooltip.razor
+++ b/src/MudBlazor/Components/Chart/Parts/ChartTooltip.razor
@@ -7,24 +7,24 @@
}
-
-
+
-
-
- @Title
+
+ @Title
@if (hasSubtitle)
{
- @Subtitle
+ @Subtitle
}
diff --git a/src/MudBlazor/Components/Chart/Parts/ChartTooltip.razor.cs b/src/MudBlazor/Components/Chart/Parts/ChartTooltip.razor.cs
index 7981ee786063..2ed28bdf3e9b 100644
--- a/src/MudBlazor/Components/Chart/Parts/ChartTooltip.razor.cs
+++ b/src/MudBlazor/Components/Chart/Parts/ChartTooltip.razor.cs
@@ -2,11 +2,12 @@
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
+#nullable enable
namespace MudBlazor.Charts;
public partial class ChartTooltip : ComponentBase
{
- private double _boxWidth = -1;
+ private double _boxWidth = 40;
private ElementReference? _hoverTextTitle = null;
[Inject]
@@ -62,7 +63,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
// Uses interop to get the bounding box of the title text to determine the width of the tooltip box
var bboxTitle = await JsRuntime.InvokeAsync("mudGetSvgBBox", _hoverTextTitle);
- _boxWidth = Math.Max(bboxTitle.Width, 30) + 10; // Minimum width for the text of 30px with 10px padding (5px each side)
+ _boxWidth = Math.Max(bboxTitle?.Width ?? 0, 30) + 10; // Minimum width for the text of 30px with 10px padding (5px each side)
StateHasChanged();
}
diff --git a/src/MudBlazor/GlobalUsings.cs b/src/MudBlazor/GlobalUsings.cs
new file mode 100644
index 000000000000..e422b2651961
--- /dev/null
+++ b/src/MudBlazor/GlobalUsings.cs
@@ -0,0 +1 @@
+global using static MudBlazor.Utilities.StringHelpers;
diff --git a/src/MudBlazor/Interop/ElementSize.cs b/src/MudBlazor/Interop/ElementSize.cs
index 393c93e3db44..6417f40ecef7 100644
--- a/src/MudBlazor/Interop/ElementSize.cs
+++ b/src/MudBlazor/Interop/ElementSize.cs
@@ -14,4 +14,9 @@ public class ElementSize
/// The width of the Element.
///
public required double Width { get; init; }
+
+ ///
+ /// The timestamp of the size change, used to help filter out of order events for server mode.
+ ///
+ public long Timestamp { get; set; }
}
diff --git a/src/MudBlazor/TScripts/mudHelpers.js b/src/MudBlazor/TScripts/mudHelpers.js
index f589e7d16579..e997a62732a8 100644
--- a/src/MudBlazor/TScripts/mudHelpers.js
+++ b/src/MudBlazor/TScripts/mudHelpers.js
@@ -88,6 +88,8 @@ window.serializeParameter = (data, spec) => {
// mudGetSvgBBox is a helper function to get the size of an svgElement
window.mudGetSvgBBox = (svgElement) => {
+ if (svgElement == null) return null;
+
const bbox = svgElement.getBBox();
return {
x: bbox.x,
@@ -108,13 +110,13 @@ window.mudObserveElementSize = (dotNetReference, element, functionName = 'OnElem
// Throttled notification function.
const throttledNotify = (width, height) => {
- const now = Date.now();
- const timeSinceLast = now - lastNotifiedTime;
+ const timestamp = Date.now();
+ const timeSinceLast = timestamp - lastNotifiedTime;
if (timeSinceLast >= debounceMillis) {
// Enough time has passed, notify immediately.
- lastNotifiedTime = now;
+ lastNotifiedTime = timestamp;
try {
- dotNetReference.invokeMethodAsync(functionName, { width, height });
+ dotNetReference.invokeMethodAsync(functionName, { width, height, timestamp });
}
catch (error) {
this.logger("[MudBlazor] Error in mudObserveElementSize:", { error });
@@ -128,7 +130,7 @@ window.mudObserveElementSize = (dotNetReference, element, functionName = 'OnElem
lastNotifiedTime = Date.now();
scheduledCall = null;
try {
- dotNetReference.invokeMethodAsync(functionName, { width, height });
+ dotNetReference.invokeMethodAsync(functionName, { width, height, timestamp });
}
catch (error) {
this.logger("[MudBlazor] Error in mudObserveElementSize:", { error });
@@ -139,6 +141,8 @@ window.mudObserveElementSize = (dotNetReference, element, functionName = 'OnElem
// Create the ResizeObserver to notify on size changes.
const resizeObserver = new ResizeObserver(entries => {
+ if (element.isConnected === false) { return; } // Element is no longer in the DOM.
+
// Use the last entry's contentRect (or element's client dimensions).
let width = element.clientWidth;
let height = element.clientHeight;
diff --git a/src/MudBlazor/Utilities/StringHelpers.cs b/src/MudBlazor/Utilities/StringHelpers.cs
new file mode 100644
index 000000000000..2d8451ee405c
--- /dev/null
+++ b/src/MudBlazor/Utilities/StringHelpers.cs
@@ -0,0 +1,20 @@
+using System.Globalization;
+
+namespace MudBlazor.Utilities;
+
+#nullable enable
+internal static class StringHelpers
+{
+ ///
+ /// Converts a double value to its string representation, rounded to 4 decimal places.
+ ///
+ /// The double value to convert.
+ /// An optional format string.
+ /// The string representation of the double value.
+ public static string ToS(double value, string? format = null)
+ {
+ return string.IsNullOrEmpty(format)
+ ? Math.Round(value, 4).ToString(CultureInfo.InvariantCulture)
+ : Math.Round(value, 4).ToString(format);
+ }
+}
From fd67ee6cc2d3c64d466286527bea25b2a50ab862 Mon Sep 17 00:00:00 2001
From: "Artyom M."
Date: Sun, 30 Mar 2025 01:16:38 +0200
Subject: [PATCH 17/22] Analyzers/SourceGenerator: Update Roslyn (#11110)
---
src/MudBlazor.Analyzers/MudBlazor.Analyzers.csproj | 2 +-
.../MudBlazor.SourceGenerator.csproj | 4 ++--
.../Analyzers/ValidAttributeTests.cs | 10 +---------
3 files changed, 4 insertions(+), 12 deletions(-)
diff --git a/src/MudBlazor.Analyzers/MudBlazor.Analyzers.csproj b/src/MudBlazor.Analyzers/MudBlazor.Analyzers.csproj
index 56612da8a2de..dca630339324 100644
--- a/src/MudBlazor.Analyzers/MudBlazor.Analyzers.csproj
+++ b/src/MudBlazor.Analyzers/MudBlazor.Analyzers.csproj
@@ -8,7 +8,7 @@
-
+
diff --git a/src/MudBlazor.SourceGenerator/MudBlazor.SourceGenerator.csproj b/src/MudBlazor.SourceGenerator/MudBlazor.SourceGenerator.csproj
index 2c541a23a85e..515a320e41bf 100644
--- a/src/MudBlazor.SourceGenerator/MudBlazor.SourceGenerator.csproj
+++ b/src/MudBlazor.SourceGenerator/MudBlazor.SourceGenerator.csproj
@@ -1,4 +1,4 @@
-
+
netstandard2.0
@@ -8,7 +8,7 @@
-
+
diff --git a/src/MudBlazor.UnitTests/Analyzers/ValidAttributeTests.cs b/src/MudBlazor.UnitTests/Analyzers/ValidAttributeTests.cs
index 289b4651172d..76f21c833ad3 100644
--- a/src/MudBlazor.UnitTests/Analyzers/ValidAttributeTests.cs
+++ b/src/MudBlazor.UnitTests/Analyzers/ValidAttributeTests.cs
@@ -1,6 +1,4 @@
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using FluentAssertions;
+using FluentAssertions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Text;
@@ -136,7 +134,6 @@ public void Cleanup()
}
[Test]
- [Ignore("https://github.com/MudBlazor/MudBlazor/issues/10869")]
public void AllowLowerCaseAttributes()
{
var diagnostics = LowerCaseAttributesDiagnostics.FilterToClass(typeof(AttributeTest).FullName);
@@ -161,7 +158,6 @@ public void AllowLowerCaseAttributes()
}
[Test]
- [Ignore("https://github.com/MudBlazor/MudBlazor/issues/10869")]
public void AllowDefaultListAttributes()
{
var diagnostics = DefaultAttributesListDiagnostics.FilterToClass(typeof(AttributeTest).FullName);
@@ -190,7 +186,6 @@ public void AllowDefaultListAttributes()
}
[Test]
- [Ignore("https://github.com/MudBlazor/MudBlazor/issues/10869")]
public void AllowCustomListAttributes()
{
var diagnostics = CustomAttributesListDiagnostics.FilterToClass(typeof(AttributeTest).FullName);
@@ -218,7 +213,6 @@ public void AllowCustomListAttributes()
}
[Test]
- [Ignore("https://github.com/MudBlazor/MudBlazor/issues/10869")]
public void AllowDataAndAriaAttributes()
{
var diagnostics = DataAndAriaAttributesDiagnostics.FilterToClass(typeof(AttributeTest).FullName);
@@ -248,7 +242,6 @@ public void AllowDataAndAriaAttributes()
}
[Test]
- [Ignore("https://github.com/MudBlazor/MudBlazor/issues/10869")]
public void AllowNoAttributes()
{
var diagnostics = NoAttributesDiagnostics.FilterToClass(typeof(AttributeTest).FullName);
@@ -281,7 +274,6 @@ public void AllowNoAttributes()
}
[Test]
- [Ignore("https://github.com/MudBlazor/MudBlazor/issues/10869")]
public void AllowAnyAttributes()
{
var diagnostics = AnyAttributesDiagnostics.FilterToClass(typeof(AttributeTest).FullName);
From abfc821d3dd09a7529f90c97b548991de0de4d26 Mon Sep 17 00:00:00 2001
From: boukenka
Date: Sun, 30 Mar 2025 07:25:42 +0200
Subject: [PATCH 18/22] MudSwitch: Change track color to improve visibility on
dark theme (#11077)
Co-authored-by: marc.bogais
---
.../Pages/Components/Switch/Examples/SwitchColorExample.razor | 2 +-
src/MudBlazor/Styles/components/_switch.scss | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/MudBlazor.Docs/Pages/Components/Switch/Examples/SwitchColorExample.razor b/src/MudBlazor.Docs/Pages/Components/Switch/Examples/SwitchColorExample.razor
index b8ae09052cbb..0308003d8a00 100644
--- a/src/MudBlazor.Docs/Pages/Components/Switch/Examples/SwitchColorExample.razor
+++ b/src/MudBlazor.Docs/Pages/Components/Switch/Examples/SwitchColorExample.razor
@@ -3,7 +3,7 @@
-
+
@code{
public bool Basic_Switch1 { get; set; } = false;
diff --git a/src/MudBlazor/Styles/components/_switch.scss b/src/MudBlazor/Styles/components/_switch.scss
index 0f9238b52f9d..677224940383 100644
--- a/src/MudBlazor/Styles/components/_switch.scss
+++ b/src/MudBlazor/Styles/components/_switch.scss
@@ -42,7 +42,7 @@
z-index: -1;
transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,background-color 150ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
border-radius: 9px;
- background-color: var(--mud-palette-black);
+ background-color: var(--mud-palette-action-default);
}
}
From 76dd334ed02ae31360b9f569efb095e081620c58 Mon Sep 17 00:00:00 2001
From: Versile Johnson II <148913404+versile2@users.noreply.github.com>
Date: Sun, 30 Mar 2025 09:46:19 -0500
Subject: [PATCH 19/22] MudMenu: Autocomplete/Select adornment click no longer
causes menu to close (#11011)
---
.../Overlay/OverlayNestedFreezeTest.razor | 12 ++--
.../Components/MenuTests.cs | 70 +++++++++++++++++++
src/MudBlazor/Base/MudBaseButton.cs | 3 +-
.../Autocomplete/MudAutocomplete.razor | 2 +-
.../Components/Input/MudInput.razor.cs | 1 +
.../Components/Input/MudInputAdornment.razor | 2 +-
.../Components/Menu/MudMenu.razor.cs | 8 ++-
.../Components/Select/MudSelect.razor | 2 +-
8 files changed, 88 insertions(+), 12 deletions(-)
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Overlay/OverlayNestedFreezeTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Overlay/OverlayNestedFreezeTest.razor
index f71903a09dd5..20544efd674b 100644
--- a/src/MudBlazor.UnitTests.Viewer/TestComponents/Overlay/OverlayNestedFreezeTest.razor
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Overlay/OverlayNestedFreezeTest.razor
@@ -6,7 +6,7 @@
- I am a button
+ AutoComplete button
@@ -18,7 +18,7 @@
- I am a button
+ Select button
@@ -37,7 +37,7 @@
- I am a button
+ Autocomplete button
@@ -50,7 +50,7 @@
- I am a button
+ Select button
@@ -70,8 +70,8 @@
@code {
public static string __description__ = "Testing MudOverlay impact on MudAutoComplete when by itself, nested in mudmenu and nested in mudmenu in mudappbar";
- string _value = string.Empty;
- private string[] _states =
+ private string _value = string.Empty;
+ private readonly string[] _states =
{
"Alabama", "Alaska", "Arizona", "Arkansas", "California",
"Colorado", "Connecticut", "Delaware", "Florida", "Georgia",
diff --git a/src/MudBlazor.UnitTests/Components/MenuTests.cs b/src/MudBlazor.UnitTests/Components/MenuTests.cs
index 53c807cdef7e..9528e2321221 100644
--- a/src/MudBlazor.UnitTests/Components/MenuTests.cs
+++ b/src/MudBlazor.UnitTests/Components/MenuTests.cs
@@ -583,5 +583,75 @@ public void ClickingMenuItem_ClosesNestedMenu()
// Ensure all popovers are closed.
comp.FindAll("div.mud-popover-open").Count.Should().Be(0);
}
+
+ [Test]
+ public async Task IActivatable_Activate_Should_ToggleMenu_Except_For_InputAdornmentIconButton()
+ {
+ // Arrange
+ var comp = Context.RenderComponent(parameters => parameters
+ .Add(p => p.ActivatorContent, builder =>
+ {
+ builder.OpenComponent(0);
+ builder.AddAttribute(1, "Class", "mud-no-activator");
+ builder.CloseComponent();
+ }));
+ var menu = comp.Instance;
+ var activatable = (Interfaces.IActivatable)menu;
+
+ // Normal case
+ activatable.Activate(new object(), new MouseEventArgs());
+ comp.WaitForAssertion(() => menu.GetState(x => x.Open).Should().BeTrue("Menu should open when Activate is called with a regular object"));
+
+ // Close Menu
+ activatable.Activate(new object(), new MouseEventArgs());
+ comp.WaitForAssertion(() => menu.GetState(x => x.Open).Should().BeFalse("Menu should be closed after calling Activate again"));
+
+ // Special case with icon button
+ var classButton = comp.FindComponent().Instance;
+ activatable.Activate(classButton, new MouseEventArgs());
+ comp.WaitForAssertion(() => menu.GetState(x => x.Open).Should().BeFalse("Menu should not open when Activate is called with an mud-no-activator selector"));
+
+ // Special case with regular button
+ var compButton = Context.RenderComponent(p => p.Add(p => p.Class, "mud-no-activator")).Instance;
+ activatable.Activate(compButton, new MouseEventArgs());
+ comp.WaitForAssertion(() => menu.GetState(x => x.Open).Should().BeFalse("Menu should not open when Activate is called with an mud-no-activator selector"));
+
+ // Clean up
+ await menu.CloseMenuAsync();
+ }
+
+ [Test]
+ public void Menu_ButtonActivator()
+ {
+ var provider = Context.RenderComponent();
+ var comp = Context.RenderComponent(parameters => parameters
+ .Add(p => p.ActivatorContent, builder =>
+ {
+ builder.OpenComponent(0);
+ builder.CloseComponent();
+ }));
+
+ provider.FindAll("div.mud-popover-open").Count.Should().Be(0);
+
+ // Click the MudButton inside the ActivatorContent
+ var button = comp.Find("button.mud-button-root");
+ button.Click();
+ provider.FindAll("div.mud-popover-open").Count.Should().Be(1);
+ button.Click();
+ // Render component with MudIconButton inside ActivatorContent
+ comp = Context.RenderComponent(parameters => parameters
+ .Add(p => p.ActivatorContent, builder =>
+ {
+ builder.OpenComponent(0);
+ builder.AddAttribute(1, "Class", "mud-icon-button-activator");
+ builder.CloseComponent();
+ }));
+
+ provider.FindAll("div.mud-popover-open").Count.Should().Be(0);
+
+ // Click the MudIconButton inside the ActivatorContent
+ comp.Find("button.mud-icon-button-activator").Click();
+ provider.FindAll("div.mud-popover-open").Count.Should().Be(1);
+ }
}
}
diff --git a/src/MudBlazor/Base/MudBaseButton.cs b/src/MudBlazor/Base/MudBaseButton.cs
index 2b59d943c89c..e4a850eb3897 100644
--- a/src/MudBlazor/Base/MudBaseButton.cs
+++ b/src/MudBlazor/Base/MudBaseButton.cs
@@ -1,5 +1,4 @@
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using MudBlazor.Interfaces;
using static System.String;
diff --git a/src/MudBlazor/Components/Autocomplete/MudAutocomplete.razor b/src/MudBlazor/Components/Autocomplete/MudAutocomplete.razor
index 02f53e307b9b..6c0db0442c1c 100644
--- a/src/MudBlazor/Components/Autocomplete/MudAutocomplete.razor
+++ b/src/MudBlazor/Components/Autocomplete/MudAutocomplete.razor
@@ -3,7 +3,7 @@
@typeparam T
-
+
InputType;
diff --git a/src/MudBlazor/Components/Input/MudInputAdornment.razor b/src/MudBlazor/Components/Input/MudInputAdornment.razor
index 3eedade1fdb3..00e5c944c8fc 100644
--- a/src/MudBlazor/Components/Input/MudInputAdornment.razor
+++ b/src/MudBlazor/Components/Input/MudInputAdornment.razor
@@ -14,7 +14,7 @@
{
@if (AdornmentClick.HasDelegate)
{
-
void IActivatable.Activate(object activator, MouseEventArgs args)
{
- _ = ToggleMenuAsync(args);
+ if (activator is MudBaseButton activatorButton &&
+ (activatorButton.Class?.Contains("mud-no-activator") ?? false))
+ {
+ return;
+ }
+ ToggleMenuAsync(args).CatchAndLog();
}
+
///
/// Disposes managed and unmanaged resources.
///
diff --git a/src/MudBlazor/Components/Select/MudSelect.razor b/src/MudBlazor/Components/Select/MudSelect.razor
index 5d6e828b6b27..865e66b85c82 100644
--- a/src/MudBlazor/Components/Select/MudSelect.razor
+++ b/src/MudBlazor/Components/Select/MudSelect.razor
@@ -3,7 +3,7 @@
@inherits MudBaseInput
-
+
Date: Sun, 30 Mar 2025 16:50:02 +0200
Subject: [PATCH 20/22] Update dependency ReportGenerator to 5.4.5 (#11079)
---
src/MudBlazor.UnitTests.Docs/MudBlazor.UnitTests.Docs.csproj | 2 +-
src/MudBlazor.UnitTests/MudBlazor.UnitTests.csproj | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/MudBlazor.UnitTests.Docs/MudBlazor.UnitTests.Docs.csproj b/src/MudBlazor.UnitTests.Docs/MudBlazor.UnitTests.Docs.csproj
index e7a372cd1255..dad4dd97875a 100644
--- a/src/MudBlazor.UnitTests.Docs/MudBlazor.UnitTests.Docs.csproj
+++ b/src/MudBlazor.UnitTests.Docs/MudBlazor.UnitTests.Docs.csproj
@@ -67,7 +67,7 @@
-
+
diff --git a/src/MudBlazor.UnitTests/MudBlazor.UnitTests.csproj b/src/MudBlazor.UnitTests/MudBlazor.UnitTests.csproj
index 310e20ceeb6c..6eee73c9de8f 100644
--- a/src/MudBlazor.UnitTests/MudBlazor.UnitTests.csproj
+++ b/src/MudBlazor.UnitTests/MudBlazor.UnitTests.csproj
@@ -28,7 +28,7 @@
-
+
From a9f0fda5aba1c54426ada06a48aa04015757528b Mon Sep 17 00:00:00 2001
From: masonwheeler
Date: Sun, 30 Mar 2025 08:52:59 -0600
Subject: [PATCH 21/22] MudTreeViewItem: Fix NullReferenceException (#11108)
---
.../TreeView/TreeViewHeterogeneous.razor | 6 ++++++
src/MudBlazor.UnitTests/Components/TreeViewTests.cs | 9 +++++++++
.../Components/TreeView/MudTreeViewItem.razor.cs | 12 ++++++++----
3 files changed, 23 insertions(+), 4 deletions(-)
create mode 100644 src/MudBlazor.UnitTests.Viewer/TestComponents/TreeView/TreeViewHeterogeneous.razor
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/TreeView/TreeViewHeterogeneous.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/TreeView/TreeViewHeterogeneous.razor
new file mode 100644
index 000000000000..99793bf9ad68
--- /dev/null
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/TreeView/TreeViewHeterogeneous.razor
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/src/MudBlazor.UnitTests/Components/TreeViewTests.cs b/src/MudBlazor.UnitTests/Components/TreeViewTests.cs
index b884ccc078b5..2aafad19930b 100644
--- a/src/MudBlazor.UnitTests/Components/TreeViewTests.cs
+++ b/src/MudBlazor.UnitTests/Components/TreeViewTests.cs
@@ -1286,5 +1286,14 @@ public void TreeView_ClickItemWhileDisabled_DoesNotChangeSelectionAndExpanded()
parentItemButton.Click();
GetItemExpandedValue().Should().Be(false);
}
+
+ [Test]
+ public void TreeView_ClickHeterogeneousTreeElement_ShouldNotThrow()
+ {
+ var comp = Context.RenderComponent();
+ var l2 = comp.Find(".L2 > div.mud-treeview-item-content");
+ var act = () => l2.Click();
+ act.Should().NotThrow();
+ }
}
}
diff --git a/src/MudBlazor/Components/TreeView/MudTreeViewItem.razor.cs b/src/MudBlazor/Components/TreeView/MudTreeViewItem.razor.cs
index 584c9f6607af..a90040c2dbf7 100644
--- a/src/MudBlazor/Components/TreeView/MudTreeViewItem.razor.cs
+++ b/src/MudBlazor/Components/TreeView/MudTreeViewItem.razor.cs
@@ -438,8 +438,10 @@ private async Task OnItemClickedAsync(MouseEventArgs ev)
}
if (!GetReadOnly())
{
- Debug.Assert(MudTreeRoot != null);
- await MudTreeRoot.OnItemClickAsync(this);
+ if (MudTreeRoot is not null)
+ {
+ await MudTreeRoot.OnItemClickAsync(this);
+ }
}
await OnClick.InvokeAsync(ev);
}
@@ -457,8 +459,10 @@ private async Task OnItemDoubleClickedAsync(MouseEventArgs ev)
}
if (!GetReadOnly())
{
- Debug.Assert(MudTreeRoot != null);
- await MudTreeRoot.OnItemClickAsync(this);
+ if (MudTreeRoot is not null)
+ {
+ await MudTreeRoot.OnItemClickAsync(this);
+ }
}
await OnDoubleClick.InvokeAsync(ev);
}
From dd76e577f0758b3c9eb1bd4c7f88d509c70d9cb1 Mon Sep 17 00:00:00 2001
From: Versile Johnson II <148913404+versile2@users.noreply.github.com>
Date: Sun, 30 Mar 2025 10:48:59 -0500
Subject: [PATCH 22/22] MudDataGrid - Preserve Selection with ParameterState
(#11032)
---
.../DataGrid/DataGridEventCallbacksTest.razor | 31 +++++---
.../DataGrid/DataGridEventSelectionTest.razor | 39 ++++++++++
.../TestComponents/_README.txt | 2 +-
.../Components/DataGridTests.cs | 73 +++++++++++++++++++
.../Components/DataGrid/MudDataGrid.razor.cs | 72 ++++++++----------
5 files changed, 163 insertions(+), 54 deletions(-)
create mode 100644 src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridEventSelectionTest.razor
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridEventCallbacksTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridEventCallbacksTest.razor
index 66d1f955e8a0..0238423d5501 100644
--- a/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridEventCallbacksTest.razor
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridEventCallbacksTest.razor
@@ -1,29 +1,33 @@
-
+
+ RowClick="@OnRowClick"
+ RowContextMenuClick="@OnRowContextMenuClick"
+ SelectedItemChanged="@OnSelectedItemChanged"
+ SelectedItemsChanged="@OnSelectedItemsChanged"
+ CommittedItemChanges="@OnCommittedItemChanges"
+ StartedEditingItem="@OnStartedEditingItem"
+ CanceledEditingItem="@OnCanceledEditingItem"
+ ReadOnly="false">
@code {
+ public static string __description__ = "Used for Unit Tests to verify SelectedItem/SelectedItems Changed";
+
private IEnumerable- _items = new List
- ()
{
- new Item("A"),
- new Item("B"),
+ new Item("A"),
+ new Item("B"),
new Item("C")
};
public bool RowClicked { get; set; }
public bool RowContextMenuClicked { get; set; }
public bool SelectedItemChanged { get; set; }
+ public bool SelectedItemsChanged { get; set; }
public bool CommittedItemChanges { get; set; }
public bool StartedEditingItem { get; set; }
public bool CanceledEditingItem { get; set; }
@@ -32,7 +36,7 @@
{
RowClicked = true;
}
-
+
private void OnRowContextMenuClick(DataGridRowClickEventArgs
- args)
{
RowContextMenuClicked = true;
@@ -43,6 +47,11 @@
SelectedItemChanged = true;
}
+ private void OnSelectedItemsChanged(HashSet
- items)
+ {
+ SelectedItemsChanged = true;
+ }
+
private void OnCommittedItemChanges(Item item)
{
CommittedItemChanges = true;
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridEventSelectionTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridEventSelectionTest.razor
new file mode 100644
index 000000000000..b648e63d4c36
--- /dev/null
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridEventSelectionTest.razor
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+ @_message
+
+
+@code {
+#nullable enable
+ public static string __description__ = "A test viewer for visibility testing of row Selection";
+ private List _selectedDocuments = [];
+ private string _message = "Click select column to select the item";
+
+ private void OnSelectedItemsChanged(HashSet documents)
+ {
+ _selectedDocuments = documents.ToList();
+ _message = "OnSelectedItemsChanged was called. " + _selectedDocuments.Count() + " items selected";
+ }
+
+ private static Task> ServerReload(GridState state)
+ {
+ List items =
+ [
+ new("1", "Test"),
+ new("2", "Test2")
+ ];
+
+ return Task.FromResult(new GridData { Items = items, TotalItems = items.Count });
+ }
+
+ public record DocumentChecklistItem(string Id, string Name);
+}
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/_README.txt b/src/MudBlazor.UnitTests.Viewer/TestComponents/_README.txt
index b8b67a4c02d6..76f5386c7830 100644
--- a/src/MudBlazor.UnitTests.Viewer/TestComponents/_README.txt
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/_README.txt
@@ -5,5 +5,5 @@ It is advised to name the test component exactly the same as the bUnit testcase
In order to have the test description show up, make sure the component implements a static field __description__ in the @code section:
@code {
- public static string __description__ = " ... ";
+ public static string __description__ = "...";
}
\ No newline at end of file
diff --git a/src/MudBlazor.UnitTests/Components/DataGridTests.cs b/src/MudBlazor.UnitTests/Components/DataGridTests.cs
index 408a51609d29..3e07b0a7750d 100644
--- a/src/MudBlazor.UnitTests/Components/DataGridTests.cs
+++ b/src/MudBlazor.UnitTests/Components/DataGridTests.cs
@@ -5103,5 +5103,78 @@ public async Task DataGrid_TwoWayBind_SelectedItem_SelectedItems()
selectedItems.Count().Should().Be(2);
selectedItem.Should().Be(4);
}
+
+ [Test]
+ public async Task DataGridSelectedItemEventsTest()
+ {
+ var comp = Context.RenderComponent();
+ var dataGrid = comp.FindComponent>();
+
+ // Test single selection mode
+ dataGrid.SetParam(x => x.MultiSelection, false);
+ comp.Render();
+
+ // Select an item
+ var firstItem = dataGrid.Instance.Items.First();
+ await comp.InvokeAsync(async () => await dataGrid.Instance.SetSelectedItemAsync(true, firstItem));
+
+ // Verify events
+ comp.Instance.SelectedItemChanged.Should().BeTrue();
+ comp.Instance.SelectedItemsChanged.Should().BeTrue();
+
+ // Reset event flags
+ comp.Instance.SelectedItemChanged = false;
+ comp.Instance.SelectedItemsChanged = false;
+
+ // Deselect the item
+ await comp.InvokeAsync(async () => await dataGrid.Instance.SetSelectedItemAsync(false, firstItem));
+
+ // Verify events for deselection
+ comp.Instance.SelectedItemChanged.Should().BeTrue();
+ comp.Instance.SelectedItemsChanged.Should().BeTrue();
+
+ // Reset event flags
+ comp.Instance.SelectedItemChanged = false;
+ comp.Instance.SelectedItemsChanged = false;
+
+ // Test multi-selection mode
+ dataGrid.SetParam(x => x.MultiSelection, true);
+ comp.Render();
+
+ // Select all items
+ await comp.InvokeAsync(async () => await dataGrid.Instance.SetSelectAllAsync(true));
+
+ // Verify events
+ comp.Instance.SelectedItemChanged.Should().BeFalse();
+ comp.Instance.SelectedItemsChanged.Should().BeTrue();
+
+ // Reset event flags
+ comp.Instance.SelectedItemChanged = false;
+ comp.Instance.SelectedItemsChanged = false;
+
+ // Deselect all items
+ await comp.InvokeAsync(async () => await dataGrid.Instance.SetSelectAllAsync(false));
+
+ // Verify events for deselection
+ comp.Instance.SelectedItemChanged.Should().BeFalse();
+ comp.Instance.SelectedItemsChanged.Should().BeTrue();
+
+ // Reset event flags
+ comp.Instance.SelectedItemChanged = false;
+ comp.Instance.SelectedItemsChanged = false;
+
+ // Test row click select
+ // find first mud-table-row and second mud-table-cell
+ var firstRow = dataGrid.FindAll(".mud-table-row")[1];
+ firstRow.Click();
+
+ // Verify events for row click
+ comp.WaitForAssertion(() => comp.Instance.SelectedItemChanged.Should().BeTrue());
+ comp.Instance.SelectedItemsChanged.Should().BeTrue();
+
+ // Reset event flags
+ comp.Instance.SelectedItemChanged = false;
+ comp.Instance.SelectedItemsChanged = false;
+ }
}
}
diff --git a/src/MudBlazor/Components/DataGrid/MudDataGrid.razor.cs b/src/MudBlazor/Components/DataGrid/MudDataGrid.razor.cs
index 24186d8b8b1e..88ebfdf74f6d 100644
--- a/src/MudBlazor/Components/DataGrid/MudDataGrid.razor.cs
+++ b/src/MudBlazor/Components/DataGrid/MudDataGrid.razor.cs
@@ -1269,6 +1269,8 @@ private async Task OnSelectedItemChangedAsync(ParameterChangedEventArgs args)
}
await _selectedItemsState.SetValueAsync(Selection);
+ // doesn't fire due to hashset reference not changing, so fire it manually
+ await SelectedItemsChanged.InvokeAsync(Selection);
}
private void OnSelectedItemsChanged(ParameterChangedEventArgs> args)
@@ -1529,11 +1531,31 @@ internal async Task SetSelectedItemAsync(bool value, T item)
}
await _selectedItemsState.SetValueAsync(Selection);
+ // manually invoke due to ParameterState not seeing state change with HashSet
+ await InvokeAsync(async () => await SelectedItemsChanged.InvokeAsync(Selection));
await InvokeAsync(() => SelectedItemsChangedEvent?.Invoke(Selection));
await InvokeAsync(StateHasChanged);
}
+ ///
+ /// Set the currently selected item in the data grid.
+ ///
+ /// The item to select.
+ ///
+ /// When is true and is true , the are updated. The is also updated.
+ ///
+ public Task SetSelectedItemAsync(T item)
+ {
+ if (!SelectOnRowClick)
+ {
+ return Task.CompletedTask;
+ }
+
+ var isSelected = Selection.Contains(item);
+ return SetSelectedItemAsync(!isSelected, item);
+ }
+
internal async Task SetSelectAllAsync(bool value)
{
// nothing should happen if multiselection is false
@@ -1544,12 +1566,15 @@ internal async Task SetSelectAllAsync(bool value)
? ServerItems
: FilteredItems;
+ Selection.Clear();
if (value)
- Selection = new HashSet(items, Comparer);
- else
- Selection.Clear();
+ {
+ Selection.UnionWith(items);
+ }
await InvokeAsync(async () => await _selectedItemsState.SetValueAsync(Selection));
+ // manually invoke due to ParameterState not seeing state change with HashSet
+ await InvokeAsync(async () => await SelectedItemsChanged.InvokeAsync(Selection));
await InvokeAsync(() => SelectedItemsChangedEvent?.Invoke(Selection));
await InvokeAsync(() => SelectedAllItemsChangedEvent?.Invoke(value));
@@ -1636,9 +1661,9 @@ internal async Task OnRowClickedAsync(MouseEventArgs args, T item, int rowIndex)
await SetSelectedItemAsync(item);
}
- internal async Task OnContextMenuClickedAsync(MouseEventArgs args, T item, int rowIndex)
+ internal Task OnContextMenuClickedAsync(MouseEventArgs args, T item, int rowIndex)
{
- await RowContextMenuClick.InvokeAsync(new DataGridRowClickEventArgs(args, item, rowIndex));
+ return RowContextMenuClick.InvokeAsync(new DataGridRowClickEventArgs(args, item, rowIndex));
}
///
@@ -1831,43 +1856,6 @@ private void VirtualItemsProviderInitialize()
};
}
- ///
- /// Set the currently selected item in the data grid.
- ///
- /// The item to select.
- ///
- /// When is true and is true , the are updated. The is also updated.
- ///
- public async Task SetSelectedItemAsync(T item)
- {
- if (!SelectOnRowClick)
- return;
-
- // this is toggle logic (unselect if selected)
- if (!Selection.Remove(item))
- {
- Selection.Add(item);
- }
- else if (!MultiSelection)
- {
- await _selectedItemState.SetValueAsync(default);
- return;
- }
-
- if (MultiSelection)
- {
- await _selectedItemsState.SetValueAsync(Selection);
- SelectedItemsChangedEvent?.Invoke(Selection);
- }
- else
- {
- Selection.Remove(_selectedItemState.Value);
- }
-
- await _selectedItemState.SetValueAsync(item);
- await _selectedItemsState.SetValueAsync(Selection);
- }
-
///
/// Starts editing for the specified item.
///