diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index 21a623a9ef38..056721081dbf 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -119,14 +119,17 @@ body:
- Other
validations:
required: true
-
- - type: checkboxes
- id: pr
+ - type: dropdown
+ id: rendering-mode
attributes:
- label: Pull Request
- description: If you need this fixed soon, consider submitting a Pull Request! Our team has limited resources so we rely on community contributions. We greatly appreciate all contributions and will review them as soon as possible for inclusion in the next version!
+ label: Blazor rendering mode
+ description: Specify the Blazor rendering mode you are using.
+ multiple: false
options:
- - label: I would like to do a Pull Request
+ - WASM
+ - Server
+ - Hybrid
+ default: 0
- type: checkboxes
id: terms
attributes:
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
index b809ef3dfa49..ac7f2179322d 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -49,13 +49,6 @@ body:
label: Describe alternatives you've considered
description: Please provide a clear and concise description of any alternative solutions or features you've considered.
placeholder: Describe alternatives!
- - type: checkboxes
- id: pr
- attributes:
- label: Pull Request
- description: If you want to see this feature soon, consider submitting a Pull Request! Our team has limited resources so we rely on community contributions. We greatly appreciate all contributions and will review them as soon as possible for inclusion in the next version!
- options:
- - label: I would like to do a Pull Request
- type: checkboxes
id: terms
attributes:
diff --git a/.github/workflows/issue.yml b/.github/workflows/issue.yml
index 7a8f6f9fedfe..315707cf9e99 100644
--- a/.github/workflows/issue.yml
+++ b/.github/workflows/issue.yml
@@ -11,24 +11,6 @@ jobs:
script: |
const body = context.payload.issue.body;
let labels = [];
- if (body.includes("- [X] I would like to do a Pull Request")) {
- labels.push("wants to do a PR");
- const author = context.payload.issue.user.login;
- const comment = 'Thanks for wanting to do a PR, @' + author + ' !\n\nWe try to merge all non-breaking bugfixes and will deliberate the value of new features for the community. Please note there is no guarantee your pull request will be merged, so if you want to be sure before investing the work, feel free to contact the team first.'
- github.rest.issues.createComment({
- issue_number: context.issue.number,
- owner: context.repo.owner,
- repo: context.repo.repo,
- body: comment
- })
- github.rest.issues.addAssignees({
- issue_number: context.issue.number,
- owner: context.repo.owner,
- repo: context.repo.repo,
- assignees: [author]
- })
- console.log("Author wants to do a PR.");
- }
if (body.includes("### Feature request type\n\nNew component")) {
labels.push("new component");
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a3e2ae3659a8..76ca3bf4468f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,8 +1,12 @@
-# 
+
+
+
+
+
+
+
-
-- [](#)
- [Information and Guidelines for Contributors](#information-and-guidelines-for-contributors)
- [Code of Conduct](#code-of-conduct)
- [Minimal Prerequisites to Compile from Source](#minimal-prerequisites-to-compile-from-source)
diff --git a/README.md b/README.md
index efd05175c38c..d3a7b45b92d3 100644
--- a/README.md
+++ b/README.md
@@ -67,6 +67,10 @@ For larger features or changes, feel free to chat with us [on Discord](https://d
📚 Check out our [contribution guidelines](/CONTRIBUTING.md) to get started and learn more about how the project works.
+### Testing a PR Locally
+
+✅ If a PR fixes something you reported, [locally test a preview version](/TESTING.md) to ensure your app works as expected before the update is released.
+
## 🚀 Getting Started
We have ready-to-go templates at the [MudBlazor.Templates](https://github.com/mudblazor/Templates) repository, or follow the quick install guide to set things up manually:
diff --git a/TESTING.md b/TESTING.md
new file mode 100644
index 000000000000..e49cb1bd7d5b
--- /dev/null
+++ b/TESTING.md
@@ -0,0 +1,58 @@
+# Testing a PR Locally
+
+1. **Clone and check out the PR (replace `PR_NUMBER`):**
+
+```bash
+git clone https://github.com/MudBlazor/MudBlazor.git
+cd MudBlazor
+git fetch origin pull/PR_NUMBER/head:pr-branch
+git checkout pr-branch
+```
+
+2. **Pack MudBlazor with a custom version:**
+
+```bash
+dotnet pack src/MudBlazor/MudBlazor.csproj -c Release -o ./LocalNuGet -p:Version=8.0.0-custom
+```
+
+3. **Add a `nuget.config` in your app folder:**
+
+```xml
+
+
+
+
+
+
+
+```
+
+4. **Update your app's `.csproj`:**
+
+```xml
+
+```
+
+5. **Restore and build:**
+
+```bash
+dotnet restore
+dotnet build
+```
+
+## 🔄 Undo the changes after testing
+
+Once you're done testing, switch back to the official release by:
+
+- Removing the local source from your `nuget.config` (or deleting the file entirely if not otherwise needed).
+- Restoring the original version in your `.csproj`:
+
+```xml
+
+```
+
+Then run:
+
+```bash
+dotnet restore
+```
diff --git a/src/MudBlazor.Docs/Components/MudTeamCard.razor b/src/MudBlazor.Docs/Components/MudTeamCard.razor
index 6af2f3c928e0..0a12287ed357 100644
--- a/src/MudBlazor.Docs/Components/MudTeamCard.razor
+++ b/src/MudBlazor.Docs/Components/MudTeamCard.razor
@@ -1,37 +1,60 @@
-
-
\ No newline at end of file
diff --git a/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualWasmStandaloneCode.html b/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualWasmStandaloneCode.html
index a6ca822456bd..bb67cec5f794 100644
--- a/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualWasmStandaloneCode.html
+++ b/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualWasmStandaloneCode.html
@@ -2,7 +2,7 @@
<!-- Important: Increment the version parameter whenever you update MudBlazor to prevent caching issues -->
-<scriptsrc="_content/MudBlazor/MudBlazor.min.css?v=1"></script>
+<scriptsrc="_content/MudBlazor/MudBlazor.min.js?v=1"></script>
\ No newline at end of file
diff --git a/src/MudBlazor.Docs/Pages/Mud/Project/Team.razor b/src/MudBlazor.Docs/Pages/Mud/Project/Team.razor
index b2fd6374dfea..3a00138b3047 100644
--- a/src/MudBlazor.Docs/Pages/Mud/Project/Team.razor
+++ b/src/MudBlazor.Docs/Pages/Mud/Project/Team.razor
@@ -1,85 +1,26 @@
@page "/mud/project/team"
@inject GitHubApiClient _gitHubApiClient;
-Meet the team - MudBlazor
+Meet the Team - MudBlazor
+
- Meet the team
+ @* Preamble *@
+ Meet the Team
- MudBlazor is not a one-person show. A nice little community has emerged and a couple of highly motivated people are working on improvements and are regularly adding more components.
- Everyone is welcome to join in and contribute to make this library even more awesome than it already is.
+ MudBlazor is an open-source project built by a growing community of contributors from around the world.
+ What started as a small initiative has evolved into a mature UI framework, maintained by volunteers who review code, guide development, and keep the project moving forward.
+ Contributions are always welcome, from a single PR to ongoing involvement, and the community continues to grow with each release.
+ Read more
+ @* Core Team *@
Core Team
- The Core Team who guide, develop and direct the development of MudBlazor.
+
+ Project leads who oversee development and review contributions in their free time.
+
-
-
-
-
-
-
-
-
-
- Jonny Larsson
- Creator
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Meinrad Recheis
- Co-Creator
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- @foreach (var member in _coreTeam)
+ @foreach (var member in TeamMemberData.GetCoreTeam())
{
@@ -87,11 +28,13 @@
}
+ @* Contribution Team *@
Contribution Team
- Core Contributors who work closely with the Core Team and are actively working on MudBlazor.
-
+
+ Community members in close collaboration, recognized for their high-quality contributions.
+
- @foreach (var contributor in _contributors)
+ @foreach (var contributor in TeamMemberData.GetContributionTeam())
{
@@ -99,8 +42,11 @@
}
+ @* Awesome Coders *@
Awesome coders
- Who allowed us to use some of their code in MudBlazor.
+
+ Developers who generously allowed us to reuse parts of their code.
+
@@ -112,13 +58,18 @@
Donated the Blazored Modal source code
-
+
Donated the XmlDoc loading code for the docs
+
+ @* All Contributors *@
All contributors
+
+ A growing list of people who’ve helped shape the project.
+
@if (_githubContributors == null)
{
@@ -135,9 +86,11 @@
}
+
+
@code
@@ -148,30 +101,4 @@
_githubContributors = await _gitHubApiClient.GetContributorsAsync();
StateHasChanged();
}
-
- private readonly TeamMember[] _coreTeam = new TeamMember[]
- {
- new TeamMember { Name = "András Tangl", From = "Szombathely, Hungary", GitHub = "tungi52", Avatar = "https://avatars.githubusercontent.com/u/22996720?v=4", LinkedIn = "https://www.linkedin.com/in/andr%C3%A1s-tangl-683a20215/"},
- new TeamMember { Name = "Mike Surcouf", From = "Jersey, Channel Islands", GitHub = "mikes-gh", Avatar = "https://avatars.githubusercontent.com/u/16208742?v=4", LinkedIn = null},
- new TeamMember { Name = "Benjamin Kappel", From = "Mexico", GitHub = "just-the-benno", Avatar = "https://avatars.githubusercontent.com/u/51370361?v=4", LinkedIn = "https://www.linkedin.com/in/benjamin-kappel-558428168/"},
- new TeamMember { Name = "Jonas B", From = "Germany", GitHub = "JonBunator", Avatar = "https://avatars.githubusercontent.com/u/62108893?v=4", LinkedIn = null},
- new TeamMember { Name = "Riley Nielsen", From = "Minnesota, United States", GitHub = "Mr-Technician", Avatar = "https://avatars.githubusercontent.com/u/26885142?v=4", LinkedIn = "https://www.linkedin.com/in/riley-nielsen-a57399223/"},
- new TeamMember { Name = "Artyom Melnikov", From = "Tallinn, Harjumaa, Estonia", GitHub = "ScarletKuro", GitHubSponsor = true, Avatar = "https://avatars.githubusercontent.com/u/19953225?v=4", LinkedIn = "https://www.linkedin.com/in/artyommelnikov/"},
- new TeamMember { Name = "Daniel Chalmers", From = "United States", GitHub = "danielchalmers", Avatar = "https://avatars.githubusercontent.com/u/7112040?v=4", LinkedIn = "https://www.linkedin.com/in/daniel-c-5799252b1"},
- };
-
- private readonly TeamMember[] _contributors = new TeamMember[]
- {
- new TeamMember { Name = "Henrique Clausing", From = "Minas Gerais, Brazil", GitHub = "HClausing", Avatar = "https://avatars.githubusercontent.com/u/15158923?v=4", LinkedIn = "https://www.linkedin.com/in/henrique-clausing-cunha-45085944/"},
- new TeamMember { Name = "Porkopek", From = "Fundão, Portugal", GitHub = "porkopek", Avatar = "https://avatars.githubusercontent.com/u/13745954?v=4", LinkedIn = null},
- new TeamMember { Name = "Mehmet Can Karagöz", From = "Alanya, Turkey", GitHub = "mckaragoz", Avatar = "https://avatars.githubusercontent.com/u/78308169?v=4", LinkedIn = null},
- new TeamMember { Name = "Jon Person", From = "Colorado, United States", GitHub = "jperson2000", Avatar = "https://avatars.githubusercontent.com/u/18043079?v=4", LinkedIn = null},
- new TeamMember { Name = "Lukas Klinger", From = "Germany", GitHub = "Flaflo", Avatar = "https://avatars.githubusercontent.com/u/12973684?v=4", LinkedIn = null},
- new TeamMember { Name = "Jason Rebelo", From = "Luxembourg", GitHub = "igotinfected", GitHubSponsor = true, Avatar = "https://avatars.githubusercontent.com/u/15004223?v=4", LinkedIn = null},
- new TeamMember { Name = "Samuel Meenzen", From = "Germany", GitHub = "meenzen", Avatar = "https://avatars.githubusercontent.com/u/22305878?v=4", LinkedIn = null},
- new TeamMember { Name = "Justin Lampe", From = "Germany", GitHub = "xC0dex", Avatar = "https://avatars.githubusercontent.com/u/22918366?v=4", LinkedIn = null},
- new TeamMember { Name = "Roman Alvarez", From = "Uruguay", GitHub = "ralvarezing", Avatar = "https://avatars.githubusercontent.com/u/40799354?v=4", LinkedIn = null},
- new TeamMember { Name = "Versile Johnson", From = "Texas, United States", GitHub = "versile2", GitHubSponsor = true, Avatar = "https://avatars.githubusercontent.com/u/148913404?v=4", LinkedIn = null},
- new TeamMember { Name = "Curtis Mayers", From = "Trinidad & Tobago", GitHub = "anu6is", Avatar = "https://avatars.githubusercontent.com/u/4596077?v=4", LinkedIn = null},
- };
}
diff --git a/src/MudBlazor.Docs/Shared/Appbar.razor b/src/MudBlazor.Docs/Shared/Appbar.razor
index a55782768699..2ef0ccaae3ca 100644
--- a/src/MudBlazor.Docs/Shared/Appbar.razor
+++ b/src/MudBlazor.Docs/Shared/Appbar.razor
@@ -17,17 +17,17 @@
Get StartedDocs
- Learn More
+ Learn More
Official
-
+
MudBlazor
- Blazor component library
+ What is MudBlazor?
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridAllowUnsortedTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridAllowUnsortedTest.razor
new file mode 100644
index 000000000000..0efe1414ddd3
--- /dev/null
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridAllowUnsortedTest.razor
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+@code {
+ [Parameter]
+ public bool AllowUnsorted { get; set; } = true;
+
+ private readonly List _items = new List
+ {
+ new("C", 42, "555"),
+ new("A", 73, "7"),
+ new("B", 11, "4444"),
+ };
+
+ public class Item(string name, int value, string misc)
+ {
+ public string Name { get; set; } = name;
+ public int Value { get; set; } = value;
+ public string Misc { get; set; } = misc;
+ }
+}
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridDateOnlyFilterTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridDateOnlyFilterTest.razor
new file mode 100644
index 000000000000..8b0b73eca422
--- /dev/null
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridDateOnlyFilterTest.razor
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+@code {
+ public static string __description__ = @"Test DateOnly filtering in DataGrid";
+ public record Model(string Name, int? Age, DateOnly? HiredOn);
+
+ private readonly IEnumerable _people =
+ [
+ new("Sam", 56, new DateOnly(2020, 3, 10)),
+ new("Alicia", 54, null),
+ new("Ira", 27, new DateOnly(2011, 1, 2)),
+ new("John", 32, new DateOnly(2022, 6, 15))
+ ];
+}
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Dialog/DialogCloseOnEscapeTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Dialog/DialogCloseOnEscapeTest.razor
new file mode 100644
index 000000000000..e2234160cf6b
--- /dev/null
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Dialog/DialogCloseOnEscapeTest.razor
@@ -0,0 +1,45 @@
+@inject IDialogService DialogService
+@inject ISnackbar Snackbar
+
+Open Dialog (Test Esc Key)
+
+@code {
+ public static string __description__ = "Open Dialog -> Click overlay -> Press Esc";
+
+ private async Task OpenDialog()
+ {
+ var options = new DialogOptions
+ {
+ CloseOnEscapeKey = true,
+ BackdropClick = true,
+ };
+
+ // Define a RenderFragment for the dialog content directly
+ RenderFragment dialogContent = builder =>
+ {
+ builder.OpenComponent(0);
+ builder.AddAttribute(1, "ChildContent", (RenderFragment)(b => b.AddContent(2, "Click the overlay (I shouldn't close), then press Esc. Or click inside me, then press Esc.")));
+ builder.CloseComponent();
+ // Add a button to ensure focus can shift to an interactive element within the dialog
+ builder.OpenComponent(3);
+ builder.AddAttribute(4, "Variant", Variant.Filled);
+ builder.AddAttribute(5, "Class", "ma-4");
+ builder.AddAttribute(6, "OnClick", EventCallback.Factory.Create(this, () => Snackbar.Add("Button clicked", Severity.Info)));
+ builder.AddAttribute(7, "ChildContent", (RenderFragment)(b => b.AddContent(8, "Test Button")));
+ builder.CloseComponent();
+ };
+
+ var parameters = new DialogParameters
+ {
+ [nameof(MudDialog.DialogContent)] = dialogContent, // Pass the RenderFragment
+ [nameof(MudDialog.OnBackdropClick)] = new EventCallback(null, (MouseEventArgs args) =>
+ {
+ Snackbar.Add("Overlay clicked! Dialog should still be open. Now try pressing Escape.", Severity.Info);
+ // This handler intentionally does not close the dialog, to test refocus.
+ })
+ };
+
+ // Use the built-in MudDialog and pass content and handlers through parameters
+ var dialog = await DialogService.ShowAsync("Test Escape Key", parameters, options);
+ }
+}
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Dialog/InlineDialogTitleRefreshTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Dialog/InlineDialogTitleRefreshTest.razor
new file mode 100644
index 000000000000..971330d7b547
--- /dev/null
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Dialog/InlineDialogTitleRefreshTest.razor
@@ -0,0 +1,45 @@
+
+
+ Show Dialog
+
+
+
+
+
+
+ @DialogTitle
+
+
+
+
+
+
+
+@code {
+ public static string __description__ = "Dialog title should update along with Progress Bar content";
+
+ private readonly DialogOptions _dialogOptions = new() { FullWidth = true };
+ private int DialogProgress { get; set; } = 0;
+ private bool DialogVisible { get; set; } = false;
+ private string DialogTitle { get; set; } = "Initial state";
+
+ private async Task OpenDialog()
+ {
+ DialogVisible = true;
+
+ for (int i = 0; i <= 10; i++)
+ {
+ await Task.Delay(150); // Simulate some work
+ DialogProgress = i * 10; // Simulate progress update
+ DialogTitle = $"Progress: {DialogProgress}%";
+ StateHasChanged();
+ }
+
+ // Keep dialog open for a bit to observe final title
+ await Task.Delay(2000);
+
+ DialogVisible = false;
+ DialogProgress = 0;
+ DialogTitle = "Initial state";
+ }
+}
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/ExpansionPanel/ExpansionPanelKeyboardTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/ExpansionPanel/ExpansionPanelKeyboardTest.razor
new file mode 100644
index 000000000000..c9cf484dca70
--- /dev/null
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/ExpansionPanel/ExpansionPanelKeyboardTest.razor
@@ -0,0 +1,8 @@
+
+
+ Button 1
+
+
+ Button 2
+
+
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Highlighter/HtmlEncodeTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Highlighter/HtmlEncodeTest.razor
new file mode 100644
index 000000000000..4147855063b2
--- /dev/null
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Highlighter/HtmlEncodeTest.razor
@@ -0,0 +1,34 @@
+
+
+ @foreach (var paragraph in paragraphs)
+ {
+
+
+
+ }
+
+
+
+
+
+
+@code {
+ public static string __description__ = "Style (color and formatting) should be present";
+
+ string highlightedText = "'";
+ bool untilNextBoundary;
+ bool caseSensitive;
+ bool markup = true;
+ IEnumerable paragraphs = new List
+ {
+ $"MudBlazor is an Material Design component framework for Blazor with an emphasis on ease of use and clear structure.",
+ $"MudLists are easily customizable and scrollable lists. Make them suit your needs with avatars, icons, or something like checkboxes.",
+ $"Use mud-* classes to customize your MudBlazor components.",
+ $"'text in single quote'"
+ };
+}
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Mask/AutoFillMaskTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Mask/AutoFillMaskTest.razor
new file mode 100644
index 000000000000..f5c0892fc8a6
--- /dev/null
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Mask/AutoFillMaskTest.razor
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+@_phone
+
+
+@code {
+ private string? _phone;
+}
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Progress/MudProgressCircularWidthTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Progress/MudProgressCircularWidthTest.razor
new file mode 100644
index 000000000000..6f0bad5d6e86
--- /dev/null
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Progress/MudProgressCircularWidthTest.razor
@@ -0,0 +1,12 @@
+
+
+ @_width
+
+
+@($"Stroke-Width: {_width}")
+
+@code {
+ public static string __description__ = "Circle should render correctly with increasing stroke width";
+
+ int _width = 2;
+}
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectMultiSelectFieldChangedTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectMultiSelectFieldChangedTest.razor
new file mode 100644
index 000000000000..094fe1fffbc1
--- /dev/null
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/SelectMultiSelectFieldChangedTest.razor
@@ -0,0 +1,51 @@
+@using MudBlazor.Utilities
+
+
+
+
+ @foreach (var state in States)
+ {
+ @state
+ }
+
+
+
+@code {
+ public static string __description__ = "FieldChanged should trigger when T != string and MultiSelect=true";
+ private IEnumerable _options = new HashSet { new("Alaska") };
+
+ public State[] States { get; init; } =
+ [
+ new("Alabama"), new("Alaska"), new("Arizona"), new("Arkansas"), new("California"),
+ new("Colorado"), new("Connecticut"), new("Delaware"), new("Florida"), new("Georgia"),
+ new("Hawaii"), new("Idaho"), new("Illinois"), new("Indiana"), new("Iowa"), new("Kansas"),
+ new("Kentucky"), new("Louisiana"), new("Maine"), new("Maryland"), new("Massachusetts"),
+ new("Michigan"), new("Minnesota"), new("Mississippi"), new("Missouri"), new("Montana"),
+ new("Nebraska"), new("Nevada"), new("New Hampshire"), new("New Jersey"), new("New Mexico"),
+ new("New York"), new("North Carolina"), new("North Dakota"), new("Ohio"), new("Oklahoma"),
+ new("Oregon"), new("Pennsylvania"), new("Rhode Island"), new("South Carolina"), new("South Dakota"),
+ new("Tennessee"), new("Texas"), new("Utah"), new("Vermont"), new("Virginia"),
+ new("Washington"), new("West Virginia"), new("Wisconsin"), new("Wyoming")
+ ];
+
+ public class State(string name) : IEquatable
+ {
+
+ public string Name { get; } = name;
+
+ public bool Equals(State? other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return Name == other.Name;
+ }
+
+ public override bool Equals(object? obj) => obj is State state && Equals(state);
+
+ public override int GetHashCode() => Name.GetHashCode();
+
+ public override string ToString() => Name;
+ }
+
+ public FormFieldChangedEventArgs? FormFieldChangedEventArgs { get; private set; }
+}
diff --git a/src/MudBlazor.UnitTests/Components/AutocompleteTests.cs b/src/MudBlazor.UnitTests/Components/AutocompleteTests.cs
index 926cbfaac0a0..13c65fdf6496 100644
--- a/src/MudBlazor.UnitTests/Components/AutocompleteTests.cs
+++ b/src/MudBlazor.UnitTests/Components/AutocompleteTests.cs
@@ -1491,7 +1491,7 @@ public async Task AutocompleteStrictFalseTest(int index)
var items = comp.FindComponents>().ToArray();
items.Length.Should().Be(10);
var item = items.SingleOrDefault(x => x.Markup.Contains(californiaString));
- items.ToList().IndexOf(item).Should().Be(4);
+ items.ToList().IndexOf(item).Should().Be(5);
comp.WaitForAssertion(() => items.Single(s => s.Markup.Contains(californiaString)).Find(listItemQuerySelector).ClassList.Should().Contain(selectedItemClassName));
await comp.InvokeAsync(async () => await autocompleteComponent.Find("input").KeyUpAsync(new KeyboardEventArgs() { Key = "Escape" })); // Close autocomplete.
diff --git a/src/MudBlazor.UnitTests/Components/DataGridTests.cs b/src/MudBlazor.UnitTests/Components/DataGridTests.cs
index bb8c2b1a3fb0..bf05dd6ba7e3 100644
--- a/src/MudBlazor.UnitTests/Components/DataGridTests.cs
+++ b/src/MudBlazor.UnitTests/Components/DataGridTests.cs
@@ -2235,6 +2235,285 @@ public void FilterDefinitionDateTimeTest()
func.Invoke(new("Joe", 45, null, null, null, null)).Should().BeTrue();
}
+ [Test]
+ public void FilterDefinitionDateOnlyTest()
+ {
+ var comp = Context.RenderComponent();
+ var dataGrid = comp.FindComponent>();
+ var dateColumn = dataGrid.Instance.GetColumnByPropertyName("HiredOn");
+ var testDate = new DateOnly(2020, 3, 10);
+
+ #region FilterOperator.DateOnly.Is
+
+ var filterDefinition = new FilterDefinition
+ {
+ Column = dateColumn,
+ Operator = FilterOperator.DateOnly.Is,
+ Value = testDate
+ };
+ var func = filterDefinition.GenerateFilterFunction(new FilterOptions
+ {
+ FilterCaseSensitivity = dataGrid.Instance.FilterCaseSensitivity
+ });
+ func.Invoke(new("Sam", 56, testDate)).Should().BeTrue();
+ func.Invoke(new("Joe", 32, null)).Should().BeFalse();
+ func.Invoke(new("Ira", 27, new DateOnly(2011, 1, 2))).Should().BeFalse();
+
+ // null value
+ filterDefinition = new FilterDefinition
+ {
+ Column = dateColumn,
+ Operator = FilterOperator.DateOnly.Is,
+ Value = null
+ };
+ func = filterDefinition.GenerateFilterFunction(new FilterOptions
+ {
+ FilterCaseSensitivity = dataGrid.Instance.FilterCaseSensitivity
+ });
+ func.Invoke(new("Sam", 56, testDate)).Should().BeTrue();
+ func.Invoke(new("Joe", 32, null)).Should().BeTrue();
+
+ #endregion
+
+ #region FilterOperator.DateOnly.IsNot
+
+ filterDefinition = new FilterDefinition
+ {
+ Column = dateColumn,
+ Operator = FilterOperator.DateOnly.IsNot,
+ Value = testDate
+ };
+ func = filterDefinition.GenerateFilterFunction(new FilterOptions
+ {
+ FilterCaseSensitivity = dataGrid.Instance.FilterCaseSensitivity
+ });
+ func.Invoke(new("Sam", 56, testDate)).Should().BeFalse();
+ func.Invoke(new("Joe", 32, null)).Should().BeTrue();
+ func.Invoke(new("Ira", 27, new DateOnly(2011, 1, 2))).Should().BeTrue();
+
+ // null value
+ filterDefinition = new FilterDefinition
+ {
+ Column = dateColumn,
+ Operator = FilterOperator.DateOnly.IsNot,
+ Value = null
+ };
+ func = filterDefinition.GenerateFilterFunction(new FilterOptions
+ {
+ FilterCaseSensitivity = dataGrid.Instance.FilterCaseSensitivity
+ });
+ func.Invoke(new("Sam", 56, testDate)).Should().BeTrue();
+ func.Invoke(new("Joe", 32, null)).Should().BeTrue();
+
+ #endregion
+
+ #region FilterOperator.DateOnly.After
+
+ filterDefinition = new FilterDefinition
+ {
+ Column = dateColumn,
+ Operator = FilterOperator.DateOnly.After,
+ Value = testDate
+ };
+ func = filterDefinition.GenerateFilterFunction(new FilterOptions
+ {
+ FilterCaseSensitivity = dataGrid.Instance.FilterCaseSensitivity
+ });
+ func.Invoke(new("Sam", 56, testDate)).Should().BeFalse();
+ func.Invoke(new("Joe", 32, null)).Should().BeFalse();
+ func.Invoke(new("John", 32, new DateOnly(2022, 6, 15))).Should().BeTrue();
+
+ // null value
+ filterDefinition = new FilterDefinition
+ {
+ Column = dateColumn,
+ Operator = FilterOperator.DateOnly.After,
+ Value = null
+ };
+ func = filterDefinition.GenerateFilterFunction(new FilterOptions
+ {
+ FilterCaseSensitivity = dataGrid.Instance.FilterCaseSensitivity
+ });
+ func.Invoke(new("Sam", 56, testDate)).Should().BeTrue();
+ func.Invoke(new("Joe", 32, null)).Should().BeTrue();
+
+ #endregion
+
+ #region FilterOperator.DateOnly.OnOrAfter
+
+ filterDefinition = new FilterDefinition
+ {
+ Column = dateColumn,
+ Operator = FilterOperator.DateOnly.OnOrAfter,
+ Value = testDate
+ };
+ func = filterDefinition.GenerateFilterFunction(new FilterOptions
+ {
+ FilterCaseSensitivity = dataGrid.Instance.FilterCaseSensitivity
+ });
+ func.Invoke(new("Sam", 56, testDate)).Should().BeTrue();
+ func.Invoke(new("Joe", 32, null)).Should().BeFalse();
+ func.Invoke(new("John", 32, new DateOnly(2022, 6, 15))).Should().BeTrue();
+
+ // null value
+ filterDefinition = new FilterDefinition
+ {
+ Column = dateColumn,
+ Operator = FilterOperator.DateOnly.OnOrAfter,
+ Value = null
+ };
+ func = filterDefinition.GenerateFilterFunction(new FilterOptions
+ {
+ FilterCaseSensitivity = dataGrid.Instance.FilterCaseSensitivity
+ });
+ func.Invoke(new("Sam", 56, testDate)).Should().BeTrue();
+ func.Invoke(new("Joe", 32, null)).Should().BeTrue();
+
+ #endregion
+
+ #region FilterOperator.DateOnly.Before
+
+ filterDefinition = new FilterDefinition
+ {
+ Column = dateColumn,
+ Operator = FilterOperator.DateOnly.Before,
+ Value = testDate
+ };
+ func = filterDefinition.GenerateFilterFunction(new FilterOptions
+ {
+ FilterCaseSensitivity = dataGrid.Instance.FilterCaseSensitivity
+ });
+ func.Invoke(new("Sam", 56, testDate)).Should().BeFalse();
+ func.Invoke(new("Joe", 32, null)).Should().BeFalse();
+ func.Invoke(new("Ira", 27, new DateOnly(2011, 1, 2))).Should().BeTrue();
+
+ // null value
+ filterDefinition = new FilterDefinition
+ {
+ Column = dateColumn,
+ Operator = FilterOperator.DateOnly.Before,
+ Value = null
+ };
+ func = filterDefinition.GenerateFilterFunction(new FilterOptions
+ {
+ FilterCaseSensitivity = dataGrid.Instance.FilterCaseSensitivity
+ });
+ func.Invoke(new("Sam", 56, testDate)).Should().BeTrue();
+ func.Invoke(new("Joe", 32, null)).Should().BeTrue();
+
+ #endregion
+
+ #region FilterOperator.DateOnly.OnOrBefore
+
+ filterDefinition = new FilterDefinition
+ {
+ Column = dateColumn,
+ Operator = FilterOperator.DateOnly.OnOrBefore,
+ Value = testDate
+ };
+ func = filterDefinition.GenerateFilterFunction(new FilterOptions
+ {
+ FilterCaseSensitivity = dataGrid.Instance.FilterCaseSensitivity
+ });
+ func.Invoke(new("Sam", 56, testDate)).Should().BeTrue();
+ func.Invoke(new("Joe", 32, null)).Should().BeFalse();
+ func.Invoke(new("Ira", 27, new DateOnly(2011, 1, 2))).Should().BeTrue();
+
+ // null value
+ filterDefinition = new FilterDefinition
+ {
+ Column = dateColumn,
+ Operator = FilterOperator.DateOnly.OnOrBefore,
+ Value = null
+ };
+ func = filterDefinition.GenerateFilterFunction(new FilterOptions
+ {
+ FilterCaseSensitivity = dataGrid.Instance.FilterCaseSensitivity
+ });
+ func.Invoke(new("Sam", 56, testDate)).Should().BeTrue();
+ func.Invoke(new("Joe", 32, null)).Should().BeTrue();
+
+ #endregion
+
+ #region FilterOperator.DateOnly.Empty
+
+ filterDefinition = new FilterDefinition
+ {
+ Column = dateColumn,
+ Operator = FilterOperator.DateOnly.Empty,
+ Value = testDate
+ };
+ func = filterDefinition.GenerateFilterFunction(new FilterOptions
+ {
+ FilterCaseSensitivity = dataGrid.Instance.FilterCaseSensitivity
+ });
+ func.Invoke(new("Sam", 56, testDate)).Should().BeFalse();
+ func.Invoke(new("Joe", 32, null)).Should().BeTrue();
+ func.Invoke(new("Ira", 27, new DateOnly(2011, 1, 2))).Should().BeFalse();
+
+ // null value
+ filterDefinition = new FilterDefinition
+ {
+ Column = dateColumn,
+ Operator = FilterOperator.DateOnly.Empty,
+ Value = null
+ };
+ func = filterDefinition.GenerateFilterFunction(new FilterOptions
+ {
+ FilterCaseSensitivity = dataGrid.Instance.FilterCaseSensitivity
+ });
+ func.Invoke(new("Sam", 56, testDate)).Should().BeFalse();
+ func.Invoke(new("Joe", 32, null)).Should().BeTrue();
+
+ #endregion
+
+ #region FilterOperator.DateOnly.NotEmpty
+
+ filterDefinition = new FilterDefinition
+ {
+ Column = dateColumn,
+ Operator = FilterOperator.DateOnly.NotEmpty,
+ Value = testDate
+ };
+ func = filterDefinition.GenerateFilterFunction(new FilterOptions
+ {
+ FilterCaseSensitivity = dataGrid.Instance.FilterCaseSensitivity
+ });
+ func.Invoke(new("Sam", 56, testDate)).Should().BeTrue();
+ func.Invoke(new("Joe", 32, null)).Should().BeFalse();
+ func.Invoke(new("Ira", 27, new DateOnly(2011, 1, 2))).Should().BeTrue();
+
+ // null value
+ filterDefinition = new FilterDefinition
+ {
+ Column = dateColumn,
+ Operator = FilterOperator.DateOnly.NotEmpty,
+ Value = null
+ };
+ func = filterDefinition.GenerateFilterFunction(new FilterOptions
+ {
+ FilterCaseSensitivity = dataGrid.Instance.FilterCaseSensitivity
+ });
+ func.Invoke(new("Sam", 56, testDate)).Should().BeTrue();
+ func.Invoke(new("Joe", 32, null)).Should().BeFalse();
+
+ #endregion
+
+ // null operator
+ filterDefinition = new FilterDefinition
+ {
+ Column = dateColumn,
+ Operator = null,
+ Value = testDate
+ };
+ func = filterDefinition.GenerateFilterFunction(new FilterOptions
+ {
+ FilterCaseSensitivity = dataGrid.Instance.FilterCaseSensitivity
+ });
+ func.Invoke(new("Sam", 56, testDate)).Should().BeTrue();
+ func.Invoke(new("Joe", 32, null)).Should().BeTrue();
+ }
+
[Test]
public void FilterDefinitionNumberTest()
{
@@ -2511,6 +2790,9 @@ public async Task DataGridCloseFiltersTest()
// check the number of filters displayed in the filters panel is 1
comp.FindAll(".filters-panel .mud-grid-item.d-flex").Count.Should().Be(1);
+ // Wait for the filter panel to render properly
+ comp.WaitForState(() => comp.FindAll(".filter-operator").Count > 0, timeout: TimeSpan.FromSeconds(5));
+
await comp.Find(".filter-operator").MouseDownAsync(new MouseEventArgs());
//set operator to CONTAINS
@@ -2524,6 +2806,9 @@ public async Task DataGridCloseFiltersTest()
//set operator to NOT CONTAINS
FilterButton().Click();
+ // Wait for the filter panel to render properly
+ comp.WaitForState(() => comp.FindAll(".filter-operator").Count > 0, timeout: TimeSpan.FromSeconds(5));
+
await comp.Find(".filter-operator").MouseDownAsync(new MouseEventArgs());
comp.FindAll(".mud-list .mud-list-item")[1].Click();
@@ -4936,5 +5221,61 @@ public void DataGrid_HierarchyExpandSingleRowTest()
dataGrid.Instance._openHierarchies.First().Should().Be(item);
}
+
+ [Test]
+ public async Task DataGridShouldAllowUnsortedAscDescOnly()
+ {
+ var comp = Context.RenderComponent(parameters => parameters
+ .Add(p => p.AllowUnsorted, false));
+ var dataGrid = comp.FindComponent>();
+ var headerCell = dataGrid.FindComponents>()[0];
+
+ await comp.InvokeAsync(() => headerCell.Instance.SortChangedAsync(new MouseEventArgs() { Button = 0 }));
+ headerCell.Instance.SortDirection.Should().Be(SortDirection.Ascending);
+ var cells = dataGrid.FindAll("td");
+ cells[0].TextContent.Should().Be("A");
+ cells[3].TextContent.Should().Be("B");
+ cells[6].TextContent.Should().Be("C");
+
+ await comp.InvokeAsync(() => headerCell.Instance.SortChangedAsync(new MouseEventArgs() { Button = 0 }));
+ headerCell.Instance.SortDirection.Should().Be(SortDirection.Descending);
+ cells = dataGrid.FindAll("td");
+ cells[0].TextContent.Should().Be("C");
+ cells[3].TextContent.Should().Be("B");
+ cells[6].TextContent.Should().Be("A");
+
+ await comp.InvokeAsync(() => headerCell.Instance.SortChangedAsync(new MouseEventArgs() { Button = 0 }));
+ headerCell.Instance.SortDirection.Should().Be(SortDirection.Ascending);
+ cells = dataGrid.FindAll("td");
+ cells[0].TextContent.Should().Be("A");
+ cells[3].TextContent.Should().Be("B");
+ cells[6].TextContent.Should().Be("C");
+
+ comp = Context.RenderComponent(parameters => parameters
+ .Add(p => p.AllowUnsorted, true));
+ dataGrid = comp.FindComponent>();
+ headerCell = dataGrid.FindComponents>()[0];
+
+ await comp.InvokeAsync(() => headerCell.Instance.SortChangedAsync(new MouseEventArgs() { Button = 0 }));
+ headerCell.Instance.SortDirection.Should().Be(SortDirection.Ascending);
+ cells = dataGrid.FindAll("td");
+ cells[0].TextContent.Should().Be("A");
+ cells[3].TextContent.Should().Be("B");
+ cells[6].TextContent.Should().Be("C");
+
+ await comp.InvokeAsync(() => headerCell.Instance.SortChangedAsync(new MouseEventArgs() { Button = 0 }));
+ headerCell.Instance.SortDirection.Should().Be(SortDirection.Descending);
+ cells = dataGrid.FindAll("td");
+ cells[0].TextContent.Should().Be("C");
+ cells[3].TextContent.Should().Be("B");
+ cells[6].TextContent.Should().Be("A");
+
+ await comp.InvokeAsync(() => headerCell.Instance.SortChangedAsync(new MouseEventArgs() { Button = 0 }));
+ headerCell.Instance.SortDirection.Should().Be(SortDirection.None);
+ cells = dataGrid.FindAll("td");
+ cells[0].TextContent.Should().Be("C");
+ cells[3].TextContent.Should().Be("A");
+ cells[6].TextContent.Should().Be("B");
+ }
}
}
diff --git a/src/MudBlazor.UnitTests/Components/DialogTests.cs b/src/MudBlazor.UnitTests/Components/DialogTests.cs
index d76adfcd0a9b..545083f7c278 100644
--- a/src/MudBlazor.UnitTests/Components/DialogTests.cs
+++ b/src/MudBlazor.UnitTests/Components/DialogTests.cs
@@ -1,8 +1,11 @@
using Bunit;
using FluentAssertions;
+using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.Extensions.DependencyInjection;
-using MudBlazor.UnitTests.TestComponents;
+using Microsoft.JSInterop;
+using Microsoft.JSInterop.Infrastructure;
+using Moq;
using MudBlazor.UnitTests.TestComponents.Dialog;
using NUnit.Framework;
@@ -148,7 +151,7 @@ public void InlineDialogShowMethodTest()
// close by click on ok button
comp.Find(".mud-message-box button").Click();
- comp.Markup.Trim().Should().BeEmpty();
+ comp.WaitForAssertion(() => comp.Markup.Trim().Should().BeEmpty(), timeout: TimeSpan.FromSeconds(5));
}
///
@@ -1341,8 +1344,124 @@ public async Task DuplicateDialogTest()
await Task.Delay(1000);
comp.FindComponents().Count.Should().Be(1);
}
- }
+ [Test]
+ public async Task Dialog_TitleContent_And_ProgressBar_ShouldUpdate()
+ {
+ var comp = Context.RenderComponent();
+ comp.Markup.Trim().Should().BeEmpty();
+
+ var service = Context.Services.GetRequiredService();
+
+ var comp1 = Context.RenderComponent();
+
+ comp1.Find("button").Click();
+
+ var dialog = comp.Find("div.mud-dialog");
+
+ dialog.Should().NotBeNull();
+ dialog.TextContent.Trim().Should().Be("Initial state"); // Initial title
+ comp.FindComponent().Instance.Value.Should().Be(0); // Initial progress value - 0
+
+ // Wait for mid-progress (simulate that loop is progressing)
+ await Task.Delay(1000);
+ //comp.Render();
+
+ dialog = comp.Find("div.mud-dialog");
+ dialog.TextContent.Should().Contain("Progress"); //change from "Initial state" to "Progress"
+
+ var progressComponent = comp.FindComponent();
+ var progressValue = progressComponent.Instance.Value;
+ progressValue.Should().BeGreaterThan(0).And.BeLessThan(100);
+
+ // Wait for dialog to close and state reset
+ await Task.Delay(3000); // Ensure loop finishes
+ comp.Render();
+
+ // Assert the dialog is now hidden
+ comp.Markup.Trim().Should().NotContain("mud-dialog");
+ }
+
+ [Test]
+ public async Task HandleBackgroundClickAsync_Should_Call_RefocusDialogAsync_When_BackdropClick_Disabled()
+ {
+ // Arrange
+ IDialogReference dialogReference = null;
+
+ var jsRuntimeMock = new Mock();
+
+ jsRuntimeMock.Setup(x => x.InvokeAsync("Blazor._internal.domWrapper.focus", It.IsAny
public bool IsDateTime { get; init; }
+ ///
+ /// Whether the represents a date only.
+ ///
+ public bool IsDateOnly { get; init; }
+
///
/// Whether the represents a true/false value.
///
@@ -59,6 +64,7 @@ public static FieldType Identify(Type? type)
IsNumber = TypeIdentifier.IsNumber(type),
IsEnum = TypeIdentifier.IsEnum(type),
IsDateTime = TypeIdentifier.IsDateTime(type),
+ IsDateOnly = TypeIdentifier.IsDateOnly(type),
IsBoolean = TypeIdentifier.IsBoolean(type),
IsGuid = TypeIdentifier.IsGuid(type)
};
diff --git a/src/MudBlazor/Components/DataGrid/FilterExpressionGenerator.cs b/src/MudBlazor/Components/DataGrid/FilterExpressionGenerator.cs
index 8f314cf37cbd..e2b24cfc3f29 100644
--- a/src/MudBlazor/Components/DataGrid/FilterExpressionGenerator.cs
+++ b/src/MudBlazor/Components/DataGrid/FilterExpressionGenerator.cs
@@ -103,6 +103,25 @@ public static class FilterExpressionGenerator
};
}
+ if (fieldType.IsDateOnly)
+ {
+ if (filter.Value is null && filter.Operator != FilterOperator.DateOnly.Empty && filter.Operator != FilterOperator.DateOnly.NotEmpty)
+ return x => true;
+
+ return filter.Operator switch
+ {
+ FilterOperator.DateOnly.Is => propertyExpression.GenerateBinary(ExpressionType.Equal, filter.Value),
+ FilterOperator.DateOnly.IsNot => propertyExpression.GenerateBinary(ExpressionType.NotEqual, filter.Value),
+ FilterOperator.DateOnly.After => propertyExpression.GenerateBinary(ExpressionType.GreaterThan, filter.Value),
+ FilterOperator.DateOnly.OnOrAfter => propertyExpression.GenerateBinary(ExpressionType.GreaterThanOrEqual, filter.Value),
+ FilterOperator.DateOnly.Before => propertyExpression.GenerateBinary(ExpressionType.LessThan, filter.Value),
+ FilterOperator.DateOnly.OnOrBefore => propertyExpression.GenerateBinary(ExpressionType.LessThanOrEqual, filter.Value),
+ FilterOperator.DateOnly.Empty => propertyExpression.GenerateBinary(ExpressionType.Equal, null),
+ FilterOperator.DateOnly.NotEmpty => propertyExpression.GenerateBinary(ExpressionType.NotEqual, null),
+ _ => x => true
+ };
+ }
+
if (fieldType.IsDateTime)
{
if (filter.Value is null && filter.Operator != FilterOperator.DateTime.Empty && filter.Operator != FilterOperator.DateTime.NotEmpty)
diff --git a/src/MudBlazor/Components/DataGrid/FilterHeaderCell.razor b/src/MudBlazor/Components/DataGrid/FilterHeaderCell.razor
index 02c730712bd6..e75c876ce733 100644
--- a/src/MudBlazor/Components/DataGrid/FilterHeaderCell.razor
+++ b/src/MudBlazor/Components/DataGrid/FilterHeaderCell.razor
@@ -42,11 +42,15 @@
@Localizer[LanguageResource.MudDataGrid_False]
}
+ else if (fieldType.IsDateOnly && !(@operator ?? "").EndsWith("empty"))
+ {
+
+ }
else if (fieldType.IsDateTime && !(@operator ?? "").EndsWith("empty"))
{
-
+
diff --git a/src/MudBlazor/Components/DataGrid/FilterHeaderCell.razor.cs b/src/MudBlazor/Components/DataGrid/FilterHeaderCell.razor.cs
index 0a7566adfdfb..d4babc69793e 100644
--- a/src/MudBlazor/Components/DataGrid/FilterHeaderCell.razor.cs
+++ b/src/MudBlazor/Components/DataGrid/FilterHeaderCell.razor.cs
@@ -75,7 +75,8 @@ private IReadOnlyCollection operators
private double? valueNumber => fieldType.IsNumber ? (double?)Column.FilterContext.FilterDefinition.Value : default;
private bool? valueBool => fieldType.IsBoolean && Column.FilterContext.FilterDefinition.Value is not null ? (bool?)Column.FilterContext.FilterDefinition.Value : default;
private Enum valueEnum => fieldType.IsEnum && Column.FilterContext.FilterDefinition.Value is not null ? (Enum)Column.FilterContext.FilterDefinition.Value : default;
- private DateTime? valueDate => fieldType.IsDateTime ? (DateTime?)Column.FilterContext.FilterDefinition.Value : default;
+ private DateTime? valueDateTimeForPicker => fieldType.IsDateTime ? (DateTime?)Column.FilterContext.FilterDefinition.Value : default;
+ private DateTime? valueDateOnlyForPicker => fieldType.IsDateOnly && Column.FilterContext.FilterDefinition.Value != null ? ((DateOnly)Column.FilterContext.FilterDefinition.Value).ToDateTime(TimeOnly.MinValue) : null;
private TimeSpan? valueTime => fieldType.IsDateTime && Column.FilterContext.FilterDefinition.Value is not null ? ((DateTime?)Column.FilterContext.FilterDefinition.Value).Value.TimeOfDay : null;
private string @operator => Column.FilterContext.FilterDefinition.Operator ?? operators.FirstOrDefault();
@@ -118,8 +119,9 @@ internal async Task BoolValueChangedAsync(bool? value)
await ApplyFilterAsync(Column.FilterContext.FilterDefinition);
}
- internal async Task DateValueChangedAsync(DateTime? value)
+ internal async Task DateTimeValueChangedAsync(DateTime? value)
{
+ // For DateTime fields, handle both date and time components
if (value != null)
{
var date = value.Value.Date;
@@ -127,29 +129,44 @@ internal async Task DateValueChangedAsync(DateTime? value)
// get the time component and add it to the date.
if (valueTime != null)
{
- date.Add(valueTime.Value);
+ date = date.Add(valueTime.Value);
}
Column.FilterContext.FilterDefinition.Value = date;
- await ApplyFilterAsync(Column.FilterContext.FilterDefinition);
}
else
{
Column.FilterContext.FilterDefinition.Value = value;
- await ApplyFilterAsync(Column.FilterContext.FilterDefinition);
}
+
+ await ApplyFilterAsync(Column.FilterContext.FilterDefinition);
+ }
+
+ internal async Task DateOnlyValueChangedAsync(DateTime? value)
+ {
+ // For DateOnly fields, convert DateTime to DateOnly
+ if (value != null)
+ {
+ var dateOnly = DateOnly.FromDateTime(value.Value);
+ Column.FilterContext.FilterDefinition.Value = dateOnly;
+ }
+ else
+ {
+ Column.FilterContext.FilterDefinition.Value = null;
+ }
+ await ApplyFilterAsync(Column.FilterContext.FilterDefinition);
}
internal async Task TimeValueChangedAsync(TimeSpan? value)
{
- if (valueDate != null)
+ if (valueDateTimeForPicker != null)
{
- var date = valueDate.Value.Date;
+ var date = valueDateTimeForPicker.Value.Date;
// get the time component and add it to the date.
- if (valueTime != null)
+ if (value != null)
{
- date = date.Add(valueTime.Value);
+ date = date.Add(value.Value);
}
Column.FilterContext.FilterDefinition.Value = date;
diff --git a/src/MudBlazor/Components/DataGrid/FilterOperator.cs b/src/MudBlazor/Components/DataGrid/FilterOperator.cs
index e16f1e0e0df0..68d8f580b016 100644
--- a/src/MudBlazor/Components/DataGrid/FilterOperator.cs
+++ b/src/MudBlazor/Components/DataGrid/FilterOperator.cs
@@ -184,6 +184,52 @@ public static class DateTime
public const string NotEmpty = "is not empty";
}
+ ///
+ /// Represents filters which are available for date only values.
+ ///
+ public static class DateOnly
+ {
+ ///
+ /// Find values matching the filter date.
+ ///
+ public const string Is = "is";
+
+ ///
+ /// Find values different from the filter date.
+ ///
+ public const string IsNot = "is not";
+
+ ///
+ /// Find values after the filter date.
+ ///
+ public const string After = "is after";
+
+ ///
+ /// Find values on or after the filter date.
+ ///
+ public const string OnOrAfter = "is on or after";
+
+ ///
+ /// Find values before the filter date.
+ ///
+ public const string Before = "is before";
+
+ ///
+ /// Find values on or before the filter date.
+ ///
+ public const string OnOrBefore = "is on or before";
+
+ ///
+ /// Find null values.
+ ///
+ public const string Empty = "is empty";
+
+ ///
+ /// Find any non-null value.
+ ///
+ public const string NotEmpty = "is not empty";
+ }
+
///
/// Represents filters which are available for Guid values.
///
@@ -250,6 +296,20 @@ internal static string[] GetOperatorByDataType(FieldType fieldType)
Boolean.Is,
};
}
+ if (fieldType.IsDateOnly)
+ {
+ return new[]
+ {
+ DateOnly.Is,
+ DateOnly.IsNot,
+ DateOnly.After,
+ DateOnly.OnOrAfter,
+ DateOnly.Before,
+ DateOnly.OnOrBefore,
+ DateOnly.Empty,
+ DateOnly.NotEmpty,
+ };
+ }
if (fieldType.IsDateTime)
{
return new[]
diff --git a/src/MudBlazor/Components/DataGrid/HeaderCell.razor.cs b/src/MudBlazor/Components/DataGrid/HeaderCell.razor.cs
index f6a79585edfc..9d392712cd61 100644
--- a/src/MudBlazor/Components/DataGrid/HeaderCell.razor.cs
+++ b/src/MudBlazor/Components/DataGrid/HeaderCell.razor.cs
@@ -376,7 +376,9 @@ internal async Task SortChangedAsync(MouseEventArgs args)
SortDirection = SortDirection switch
{
SortDirection.Ascending => SortDirection.Descending,
- SortDirection.Descending => SortDirection.None,
+ SortDirection.Descending => DataGrid?.AllowUnsorted ?? false
+ ? SortDirection.None
+ : SortDirection.Ascending,
_ => SortDirection.Ascending
};
@@ -386,9 +388,9 @@ internal async Task SortChangedAsync(MouseEventArgs args)
return;
}
- DataGrid.DropContainerHasChanged();
+ DataGrid?.DropContainerHasChanged();
- if ((args.MetaKey || 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));
diff --git a/src/MudBlazor/Components/DataGrid/MudDataGrid.razor.cs b/src/MudBlazor/Components/DataGrid/MudDataGrid.razor.cs
index 2ccad9b67d51..7ffd277efd98 100644
--- a/src/MudBlazor/Components/DataGrid/MudDataGrid.razor.cs
+++ b/src/MudBlazor/Components/DataGrid/MudDataGrid.razor.cs
@@ -1101,6 +1101,15 @@ public bool Groupable
#endregion
+ ///
+ /// Allows a sort direction of in addition to and .
+ ///
+ ///
+ /// Defaults to true. When false, the sort mode will only toggle between and .
+ ///
+ [Parameter]
+ public bool AllowUnsorted { get; set; } = true;
+
#region Properties
internal IEnumerable CurrentPageItems
diff --git a/src/MudBlazor/Components/DataGrid/TypeIdentifier.cs b/src/MudBlazor/Components/DataGrid/TypeIdentifier.cs
index 64d111268504..680824d4c25b 100644
--- a/src/MudBlazor/Components/DataGrid/TypeIdentifier.cs
+++ b/src/MudBlazor/Components/DataGrid/TypeIdentifier.cs
@@ -88,6 +88,23 @@ public static bool IsDateTime(Type? type)
return underlyingType is not null && underlyingType == typeof(DateTime);
}
+ public static bool IsDateOnly(Type? type)
+ {
+ if (type is null)
+ {
+ return false;
+ }
+
+ if (type == typeof(DateOnly))
+ {
+ return true;
+ }
+
+ var underlyingType = Nullable.GetUnderlyingType(type);
+
+ return underlyingType is not null && underlyingType == typeof(DateOnly);
+ }
+
public static bool IsBoolean(Type? type)
{
if (type is null)
diff --git a/src/MudBlazor/Components/Dialog/MudDialog.razor.cs b/src/MudBlazor/Components/Dialog/MudDialog.razor.cs
index 683bd7b335a4..7eb16b262b1a 100644
--- a/src/MudBlazor/Components/Dialog/MudDialog.razor.cs
+++ b/src/MudBlazor/Components/Dialog/MudDialog.razor.cs
@@ -275,6 +275,10 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
{
// Forward render update to instance
(_reference.Dialog as IMudStateHasChanged)?.StateHasChanged();
+
+ //forward render update to instance container
+ if (_reference.Dialog is MudDialog { DialogInstance: not null } dialog)
+ await InvokeAsync(dialog.DialogInstance!.StateHasChanged);
}
else
{
diff --git a/src/MudBlazor/Components/Dialog/MudDialogContainer.razor b/src/MudBlazor/Components/Dialog/MudDialogContainer.razor
index 6ee05cd0298c..eea75629412a 100644
--- a/src/MudBlazor/Components/Dialog/MudDialogContainer.razor
+++ b/src/MudBlazor/Components/Dialog/MudDialogContainer.razor
@@ -8,8 +8,8 @@
}