diff --git a/src/.editorconfig b/src/.editorconfig index cc0c04c15397..e5bfa858d422 100644 --- a/src/.editorconfig +++ b/src/.editorconfig @@ -12,7 +12,7 @@ indent_style = space # (Please don't specify an indent_size here; that has too many unintended consequences.) # Code files -[*.{cs,csx,vb,vbx}] +[*.{cs,csx,vb,vbx,razor}] indent_size = 4 insert_final_newline = true charset = utf-8-bom diff --git a/src/MudBlazor.Docs/Pages/Components/DataGrid/DataGridPage.razor b/src/MudBlazor.Docs/Pages/Components/DataGrid/DataGridPage.razor index 287dcecdb360..9125f5c337cf 100644 --- a/src/MudBlazor.Docs/Pages/Components/DataGrid/DataGridPage.razor +++ b/src/MudBlazor.Docs/Pages/Components/DataGrid/DataGridPage.razor @@ -369,5 +369,23 @@ + + + + There are many options available for the <MudDataGrid> that are not covered in the examples above. + The <ColGroup> component allows you to apply a span or style property to multiple columns. However, + the available styles are limited to a few CSS properties, including width, visibility, background, and border. Additionally, the + `span` property enables a column to extend across multiple columns. The Loading property displays a loading + indicator while the data grid is retrieving data. You can bind this attribute to a `bool` variable set to `true` when initializing + the page and toggle it once the data has loaded. The FixedHeader property ensures that the header remains + fixed at the top of the grid, even when scrolling. These are just a few of the many available options. For a complete list and + additional details, refer to the API documentation above. + + + + + + + diff --git a/src/MudBlazor.Docs/Pages/Components/DataGrid/Examples/DataGridOtherOptionsExample.razor b/src/MudBlazor.Docs/Pages/Components/DataGrid/Examples/DataGridOtherOptionsExample.razor new file mode 100644 index 000000000000..dec475d90bd5 --- /dev/null +++ b/src/MudBlazor.Docs/Pages/Components/DataGrid/Examples/DataGridOtherOptionsExample.razor @@ -0,0 +1,35 @@ +@namespace MudBlazor.Docs.Examples + + + + + + + + + + + + + + + + + + + +@code { + private bool _dense = false; + private bool _loading = true; + private readonly List _persons = Enumerable.Repeat(0, 100) + .Select((_, i) => new Person { Id = i, Name = $"Name{i}" }) + .ToList(); + + public class Person + { + public required int Id { get; init; } + + public required string Name { get; init; } + } +} diff --git a/src/MudBlazor.Docs/Pages/Components/Stack/Examples/StackBreakpointExample.razor b/src/MudBlazor.Docs/Pages/Components/Stack/Examples/StackBreakpointExample.razor new file mode 100644 index 000000000000..be144f686927 --- /dev/null +++ b/src/MudBlazor.Docs/Pages/Components/Stack/Examples/StackBreakpointExample.razor @@ -0,0 +1,28 @@ +@namespace MudBlazor.Docs.Examples + + + + + Item 1 + Item 2 + Item 3 + + + + + + + + @foreach (var value in Enum.GetValues(typeof(Breakpoint)).Cast()){ + @value + } + + + + + +@code { + private Breakpoint _breakpoint = Breakpoint.None; + private bool _row = false; + private bool _reverse = false; +} \ No newline at end of file diff --git a/src/MudBlazor.Docs/Pages/Components/Stack/StackPage.razor b/src/MudBlazor.Docs/Pages/Components/Stack/StackPage.razor index 1abee50448c2..7dfc33fa9e26 100644 --- a/src/MudBlazor.Docs/Pages/Components/Stack/StackPage.razor +++ b/src/MudBlazor.Docs/Pages/Components/Stack/StackPage.razor @@ -24,6 +24,15 @@ + + + With the @nameof(MudStack.Breakpoint) property set, the state of the @nameof(MudStack.Row) property will be reversed based on the defined screen size breakpoint. For example, when set to a specific breakpoint such as @nameof(Breakpoint.Md), the row state is reversed at that screen size. If set to a range like @nameof(Breakpoint.MdAndUp), the row state will be reversed for all screen sizes greater than or equal to the specified breakpoint. + + + + + + The default spacing can be changed by setting any number between 0 and 16 with the @nameof(MudStack.Spacing) property. diff --git a/src/MudBlazor.Docs/Pages/Components/Tabs/Examples/TabsToolTipExample.razor b/src/MudBlazor.Docs/Pages/Components/Tabs/Examples/TabsToolTipExample.razor index a88067e5df61..4441a26bddf9 100644 --- a/src/MudBlazor.Docs/Pages/Components/Tabs/Examples/TabsToolTipExample.razor +++ b/src/MudBlazor.Docs/Pages/Components/Tabs/Examples/TabsToolTipExample.razor @@ -5,4 +5,4 @@ - + \ No newline at end of file diff --git a/src/MudBlazor.Docs/Pages/Components/Tabs/Examples/TabsVisibleExample.razor b/src/MudBlazor.Docs/Pages/Components/Tabs/Examples/TabsVisibleExample.razor new file mode 100644 index 000000000000..87c3dda1b57e --- /dev/null +++ b/src/MudBlazor.Docs/Pages/Components/Tabs/Examples/TabsVisibleExample.razor @@ -0,0 +1,15 @@ +@namespace MudBlazor.Docs.Examples + +
+ + + + + + + +
+ +@code { + private bool _visible = true; +} \ No newline at end of file diff --git a/src/MudBlazor.Docs/Pages/Components/Tabs/TabsPage.razor b/src/MudBlazor.Docs/Pages/Components/Tabs/TabsPage.razor index 21b5c364a793..34e966d791f8 100644 --- a/src/MudBlazor.Docs/Pages/Components/Tabs/TabsPage.razor +++ b/src/MudBlazor.Docs/Pages/Components/Tabs/TabsPage.razor @@ -115,6 +115,17 @@
+ + + + The Visible property defines whether the tab is visible or hidden. + + + + + + + diff --git a/src/MudBlazor.Docs/Styles/core/_markdown.scss b/src/MudBlazor.Docs/Styles/core/_markdown.scss index de5c4a64a066..83fe02d59c3c 100644 --- a/src/MudBlazor.Docs/Styles/core/_markdown.scss +++ b/src/MudBlazor.Docs/Styles/core/_markdown.scss @@ -1,6 +1,4 @@ - - -.mud-codeblock { +.mud-codeblock { height: 100%; padding: 24px; overflow: auto; diff --git a/src/MudBlazor.Docs/wwwroot/CommunityExtensions.json b/src/MudBlazor.Docs/wwwroot/CommunityExtensions.json index 1cd804bcb500..8fb1c64f1ada 100644 --- a/src/MudBlazor.Docs/wwwroot/CommunityExtensions.json +++ b/src/MudBlazor.Docs/wwwroot/CommunityExtensions.json @@ -107,5 +107,14 @@ "Link": "https://mudblocks.cc/", "GitHubUserPath": "mouse0270", "GitHubRepoPath": "mudblocks" + }, + { + "AvatarImageSrc": "https://avatars.githubusercontent.com/u/5263228?s=48&v=4", + "Category": "Components", + "Name": "MudBlazor PdfViewer", + "Description": "A component for displaying and manipulating PDF documents.", + "Link": "https://mudpdf.info", + "GitHubUserPath": "tgothorp", + "GitHubRepoPath": "MudBlazor.PdfViewer" } ] diff --git a/src/MudBlazor.UnitTests.Docs/MudBlazor.UnitTests.Docs.csproj b/src/MudBlazor.UnitTests.Docs/MudBlazor.UnitTests.Docs.csproj index 252a9f5ccdc7..b9936888a227 100644 --- a/src/MudBlazor.UnitTests.Docs/MudBlazor.UnitTests.Docs.csproj +++ b/src/MudBlazor.UnitTests.Docs/MudBlazor.UnitTests.Docs.csproj @@ -60,14 +60,14 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + - - - + + + diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Autocomplete/AutocompleteAdornmentClickHandlingTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Autocomplete/AutocompleteAdornmentClickHandlingTest.razor new file mode 100644 index 000000000000..68a9324b1a1f --- /dev/null +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Autocomplete/AutocompleteAdornmentClickHandlingTest.razor @@ -0,0 +1,53 @@ + + + + + Dialog Title + + + Dialog Content + + + + Cancel + + + + +@code { + public static string __description__ = "Adornment Click should not automatically open popover"; + + private readonly string _value = "Foo"; + private readonly string[] _values = ["Foo", "Bar", "Baz"]; + private bool _dialogVisible; + + private Task> SearchValues(string value, CancellationToken token) + { + if (string.IsNullOrEmpty(value)) + { + return Task.FromResult>(_values); + } + + var values = _values.Where(v => v.Contains(value, StringComparison.OrdinalIgnoreCase)); + + return Task.FromResult(values); + } + + private void OpenDialog() + { + _dialogVisible = true; + } + + private void CloseDialog() + { + _dialogVisible = false; + } +} diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Autocomplete/AutocompleteRetainFocusTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Autocomplete/AutocompleteRetainFocusTest.razor index 71b1655397f0..db8987124af6 100644 --- a/src/MudBlazor.UnitTests.Viewer/TestComponents/Autocomplete/AutocompleteRetainFocusTest.razor +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Autocomplete/AutocompleteRetainFocusTest.razor @@ -1,34 +1,31 @@ -@using MudBlazor -@using System.Threading - - + This is only here to push the autocompletes lower on the page to demonstrate the behaviour where they draw over a message box. - -@($"Text: {autocomp1?.Text} Value: {autocomp1?.Value} Local Value: {_messageBoxValue}") +@($"Text: {_autocomp1?.Text} Value: {_autocomp1?.Value} Local Value: {_messageBoxValue}") - - @foreach (string s in _states) + @foreach (var state in _states) { - + } -@($"Text: {select1?.Text} Value: {select1?.Value} Local Value: {_dialogValue}") +@($"Text: {_select1?.Text} Value: {_select1?.Value} Local Value: {_dialogValue}") @code { - public static string __description__ = " Test Autocomplete retaining focus and not closing properly. "; + public static string __description__ = "Test Autocomplete retaining focus and not closing properly."; private string _messageBoxValue = string.Empty; private string _dialogValue = string.Empty; private string _workaroundValue = string.Empty; - private MudAutocomplete autocomp1 = default!; - private MudSelect select1 = default!; - private MudMessageBox _msgBox = default!; + private MudAutocomplete _autocomp1 = null!; + private MudSelect _select1 = null!; + private MudMessageBox _msgBox = null!; private MudAutocomplete _workaroundAutocomplete = new(); private bool _dialogVisible; private int _rating; private readonly DialogOptions _dialogOptions = new() { FullWidth = true }; - private void OpenDialog() => _dialogVisible = true; - private void Submit() => _dialogVisible = false; - private string[] _states = + private readonly string[] _states = { "Alabama", "Alaska", "Arizona", "Arkansas", "California", "Colorado", "Connecticut", "Delaware", "Florida", "Georgia", @@ -106,11 +101,15 @@ // if text is null or empty, show complete list if (string.IsNullOrEmpty(value)) + { return _states; + } return _states.Where(x => x.Contains(value, StringComparison.InvariantCultureIgnoreCase)); } + private void Submit() => _dialogVisible = false; + private async Task OpenMessageBox(string value) { _messageBoxValue = value; @@ -139,4 +138,4 @@ await _msgBox.ShowAsync(); } } -} \ No newline at end of file +} diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Autocomplete/AutocompleteTest8.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Autocomplete/AutocompleteTest8.razor index 38a62451c64e..a34d5d5c01a9 100644 --- a/src/MudBlazor.UnitTests.Viewer/TestComponents/Autocomplete/AutocompleteTest8.razor +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Autocomplete/AutocompleteTest8.razor @@ -1,10 +1,10 @@ - -@if (MustBeShown) +@if (MustBeShown) { } + @code { public bool MustBeShown { get; set; } = true; public bool HasBeenDisposed { get; private set; } -} \ No newline at end of file +} diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridColumnShowFilterIconsTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridColumnShowFilterIconsTest.razor new file mode 100644 index 000000000000..57efe2ad7437 --- /dev/null +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridColumnShowFilterIconsTest.razor @@ -0,0 +1,26 @@ + + + + + + + + + + + + +@code { + public static string __description__ = "Grid with FilterMode = DataGridFilterMode.ColumnFilterRow and " + + "PropertyColumn.ShowFilterIcon = false should not show filter icons"; + + public record Model(string Name, int? Age, Severity? Status, bool? Hired, DateTime? HiredOn); + + private readonly IEnumerable _items = new List() + { + new("Sam", 56, Severity.Normal, false, null), + new("Alicia", 54, Severity.Info, null, null), + new("Ira", 27, Severity.Success, true, new DateTime(2011, 1, 2)), + new("John", 32, Severity.Warning, false, null) + }; +} diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridFixedHeaderFilterTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridFixedHeaderFilterTest.razor index b32196c60102..224b12746a1d 100644 --- a/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridFixedHeaderFilterTest.razor +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridFixedHeaderFilterTest.razor @@ -1,49 +1,31 @@  - - + - - + @code { - private bool _dense = false; - public static string __description__ = " Filter popover positioning when DataGrid FixedHeader is true "; - List _persons = []; - - protected override void OnInitialized() - { - for (int i = 0; i < 100; i++) - { - _persons.Add(new() { Id = i, Name = $"Name{i}" }); - } - } - - private async Task> ServerReload(GridState state) - { - await Task.CompletedTask; - - return new GridData - { - TotalItems = _persons.Count, - Items = _persons - }; - } + public static string __description__ = "Filter popover positioning when DataGrid FixedHeader is true as well as loading indicator"; + + private bool _dense; + private readonly List _persons = Enumerable.Repeat(0, 100) + .Select((_, i) => new Person { Id = i, Name = $"Name{i}" }) + .ToList(); public class Person { - public int Id { get; set; } + public required int Id { get; init; } - public string Name { get; set; } = default!; + public required string Name { get; init; } } -} \ No newline at end of file +} diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridInDialogFilterTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridInDialogFilterTest.razor index 39b4f56793a5..aca4766f3c97 100644 --- a/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridInDialogFilterTest.razor +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridInDialogFilterTest.razor @@ -19,22 +19,15 @@ @code { -#nullable enable - public static string __description__ = " Datagrid inside a Dialog making sure Filter popover shows and goes away properly. "; + public static string __description__ = "Datagrid inside a Dialog making sure Filter popover shows and goes away properly."; - private readonly List _items = new List(); - private bool _visible; + private readonly List _items = Enumerable.Repeat(0, 200) + .Select((_, i) => new Model($"Value_{i}", $"Value_{i}", $"Value_{i}", $"Value_{i}", $"Value_{i}")) + .ToList(); private readonly DialogOptions _dialogOptions = new() { FullWidth = true }; + private bool _visible; private void OpenDialog() => _visible = true; - private void Submit() => _visible = false; - - protected override void OnInitialized() - { - for (var i = 0; i < 200; i++) - _items.Add(new Model($"Value_{i}", $"Value_{i}", $"Value_{i}", $"Value_{i}", $"Value_{i}")); - } - public record Model(string Column1, string Column2, string Column3, string Column4, string Column5); } diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridUniqueRowKeyTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridUniqueRowKeyTest.razor new file mode 100644 index 000000000000..bb8a8b680ff4 --- /dev/null +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridUniqueRowKeyTest.razor @@ -0,0 +1,22 @@ + +@* The input element will be tested to see if it gets recreated when row order changes. *@ + + + + + + + + + + +@code { + [Parameter] + public bool Group { get; set; } + + private readonly IEnumerable _items = new List + { + "Item1", + "Item2", + }; +} diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/DatePicker/PersianDatePickerTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/DatePicker/PersianDatePickerTest.razor index 1c6227f240cf..1b97b041dd5e 100644 --- a/src/MudBlazor.UnitTests.Viewer/TestComponents/DatePicker/PersianDatePickerTest.razor +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/DatePicker/PersianDatePickerTest.razor @@ -4,10 +4,11 @@

Persian DatePicker

- -@code { - private DateTime? _date = new DateTime(2021, 02, 14); +@code { + [Parameter] + public DateTime? Date { get; set; } = new DateTime(2021, 02, 14); } diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Drawer/DrawerDialogSelectTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Drawer/DrawerDialogSelectTest.razor index ea9d59a6d4e0..7d7b07fee521 100644 --- a/src/MudBlazor.UnitTests.Viewer/TestComponents/Drawer/DrawerDialogSelectTest.razor +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Drawer/DrawerDialogSelectTest.razor @@ -1,11 +1,11 @@ - + Open Simple Dialog - + Open Drawer - + My App @@ -21,12 +21,11 @@ @code { - #nullable enable - public static string __description__ = " Drawer inside a Dialog with a Select inside the Drawer make sure z-index is not overridden by css. "; - private bool dialog_open; + public static string __description__ = "Drawer inside a Dialog with a Select inside the Drawer make sure z-index is not overridden by css."; + private bool _dialogOpen; private bool _open; private Anchor _anchor; - private bool _overlayAutoClose = true; + private const bool OverlayAutoClose = true; private void OpenDrawer(Anchor anchor) { diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Form/FormWithChildForm.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Form/FormWithChildForm.razor index 37b0056bbb3d..f575d19c3202 100644 --- a/src/MudBlazor.UnitTests.Viewer/TestComponents/Form/FormWithChildForm.razor +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Form/FormWithChildForm.razor @@ -1,31 +1,35 @@ - + + Parent Valid: @(_parentIsValid.ToString()) - + Display child form @if (_showChildForm) { - + Child Valid: @(_childIsValid.ToString()) - + } + @code { + public static string __description__ = "Based on issue #7066. Removing the child form should no longer influence the parent form."; private bool _parentIsValid; private bool _childIsValid; private bool _showChildForm; -} \ No newline at end of file + + public bool IsParentTouchChanged; + public bool IsChildTouchChanged; + + private void ParentTouchChanged(bool isTouched) + { + IsParentTouchChanged = isTouched; + } + + private void ChildTouchChanged(bool isTouched) + { + IsChildTouchChanged = isTouched; + } +} diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectTestRequiredValue.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectTestRequiredValue.razor index e95be5df3ea4..ce786096b5e0 100644 --- a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectTestRequiredValue.razor +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectTestRequiredValue.razor @@ -1,20 +1,40 @@  - + 1 2 3 - + + Clear + Reset + + +Value: @_stringSelect?.Value +Selected values: @((_stringSelect?.SelectedValues is null) ? null : string.Join(", ", _stringSelect.SelectedValues.Select(x => x))) + + @foreach(var role in _roles) { } + + Clear + Reset + + +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"; + public static string __description__ = "Multi-Select Required Should Recognize Values"; + + private MudSelect _stringSelect = null!; + private MudSelect _objectSelect = null!; private string? _value = null; diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Stack/BreakpointReverseRowTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Stack/BreakpointReverseRowTest.razor new file mode 100644 index 000000000000..aa9ce80914ab --- /dev/null +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Stack/BreakpointReverseRowTest.razor @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Stack/BreakpointReverseTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Stack/BreakpointReverseTest.razor new file mode 100644 index 000000000000..e4331ed246bd --- /dev/null +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Stack/BreakpointReverseTest.razor @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Stack/BreakpointRowTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Stack/BreakpointRowTest.razor new file mode 100644 index 000000000000..73731a283b2d --- /dev/null +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Stack/BreakpointRowTest.razor @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Stack/BreakpointTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Stack/BreakpointTest.razor new file mode 100644 index 000000000000..d6bd541241d5 --- /dev/null +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Stack/BreakpointTest.razor @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Table/TableSortLabelTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Table/TableSortLabelTest.razor new file mode 100644 index 000000000000..f00c0446b3a5 --- /dev/null +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Table/TableSortLabelTest.razor @@ -0,0 +1,27 @@ + + + Sortable Header + + + @context + + + +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; + + protected override void OnParametersSet() + { + base.OnParametersSet(); + + _sortEnabled = SortEnabled; + } +} diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Tabs/TabsVisibleTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Tabs/TabsVisibleTest.razor new file mode 100644 index 000000000000..1b9ece535512 --- /dev/null +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Tabs/TabsVisibleTest.razor @@ -0,0 +1,10 @@ + + + + + + +@code { + [Parameter] + public bool Visible { get; set; } = true; +} diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/TextField/TextFieldWithLongLabelTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/TextField/TextFieldWithLongLabelTest.razor new file mode 100644 index 000000000000..d70aa80afc84 --- /dev/null +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/TextField/TextFieldWithLongLabelTest.razor @@ -0,0 +1,7 @@ +
+ +
+ +@code { + public string TextValue { get; set; } = ""; +} \ No newline at end of file diff --git a/src/MudBlazor.UnitTests/Analyzers/ValidAttributeTests.cs b/src/MudBlazor.UnitTests/Analyzers/ValidAttributeTests.cs index 838334488445..289b4651172d 100644 --- a/src/MudBlazor.UnitTests/Analyzers/ValidAttributeTests.cs +++ b/src/MudBlazor.UnitTests/Analyzers/ValidAttributeTests.cs @@ -136,6 +136,7 @@ public void Cleanup() } [Test] + [Ignore("https://github.com/MudBlazor/MudBlazor/issues/10869")] public void AllowLowerCaseAttributes() { var diagnostics = LowerCaseAttributesDiagnostics.FilterToClass(typeof(AttributeTest).FullName); @@ -160,6 +161,7 @@ public void AllowLowerCaseAttributes() } [Test] + [Ignore("https://github.com/MudBlazor/MudBlazor/issues/10869")] public void AllowDefaultListAttributes() { var diagnostics = DefaultAttributesListDiagnostics.FilterToClass(typeof(AttributeTest).FullName); @@ -188,6 +190,7 @@ public void AllowDefaultListAttributes() } [Test] + [Ignore("https://github.com/MudBlazor/MudBlazor/issues/10869")] public void AllowCustomListAttributes() { var diagnostics = CustomAttributesListDiagnostics.FilterToClass(typeof(AttributeTest).FullName); @@ -215,6 +218,7 @@ public void AllowCustomListAttributes() } [Test] + [Ignore("https://github.com/MudBlazor/MudBlazor/issues/10869")] public void AllowDataAndAriaAttributes() { var diagnostics = DataAndAriaAttributesDiagnostics.FilterToClass(typeof(AttributeTest).FullName); @@ -244,6 +248,7 @@ public void AllowDataAndAriaAttributes() } [Test] + [Ignore("https://github.com/MudBlazor/MudBlazor/issues/10869")] public void AllowNoAttributes() { var diagnostics = NoAttributesDiagnostics.FilterToClass(typeof(AttributeTest).FullName); @@ -276,6 +281,7 @@ public void AllowNoAttributes() } [Test] + [Ignore("https://github.com/MudBlazor/MudBlazor/issues/10869")] public void AllowAnyAttributes() { var diagnostics = AnyAttributesDiagnostics.FilterToClass(typeof(AttributeTest).FullName); diff --git a/src/MudBlazor.UnitTests/Components/DataGridTests.cs b/src/MudBlazor.UnitTests/Components/DataGridTests.cs index ce21b3aa6a39..fd12aa54851b 100644 --- a/src/MudBlazor.UnitTests/Components/DataGridTests.cs +++ b/src/MudBlazor.UnitTests/Components/DataGridTests.cs @@ -3245,7 +3245,7 @@ await dataGrid.InvokeAsync(async () => foreach (var column in dataGrid.Instance.RenderedColumns) { await column.HiddenState.SetValueAsync(true); - }; + } }); // cannot render the component again there can be only one mudpopoverprovider @@ -3259,7 +3259,7 @@ await dataGrid.InvokeAsync(async () => foreach (var column in dataGrid.Instance.RenderedColumns) { await column.HiddenState.SetValueAsync(false); - }; + } }); // 6 columns, 0 hidden (1 permanently hidden) @@ -3359,6 +3359,17 @@ await comp.InvokeAsync(() => dataGrid.FindAll("tbody tr").Count.Should().Be(0); } + [Test] + public void DataGridColumnShowFilterIconsTest() + { + var comp = Context.RenderComponent(); + var dataGrid = comp.FindComponent>(); + + // Should have 5 columns, but only two with filter icons + dataGrid.FindComponents>().Should().HaveCount(5); + dataGrid.FindAll(".column-filter-menu").Should().HaveCount(2); + } + [Test] public async Task DataGridColumnPopupFilteringEmptyTest() { @@ -5014,9 +5025,45 @@ public void TestRtlGroupIconMethod(bool isRightToLeft, bool isExpanded) { var test = new MudDataGrid(); if (isExpanded) + { test.GetGroupIcon(isExpanded, isRightToLeft).Should().Be(Icons.Material.Filled.ExpandMore); + } else + { test.GetGroupIcon(isExpanded, isRightToLeft).Should().Be(isRightToLeft ? Icons.Material.Filled.ChevronLeft : Icons.Material.Filled.ChevronRight); + } + } + + /// + /// Verifies data grid does not reuse row child components for different items (the @key for the row is set to the user supplied item). + /// + [Test] + public async Task DataGridUniqueRowKey() + { + //Test the normal case + var comp = Context.RenderComponent(); + var dataGrid = comp.FindComponent>(); + + var sortByColumnName = dataGrid.Instance.RenderedColumns.FirstOrDefault().PropertyName; + + await comp.InvokeAsync(() => dataGrid.Instance.SetSortAsync(sortByColumnName, SortDirection.Ascending, x => x)); + var before = dataGrid.FindComponent>(); + await comp.InvokeAsync(() => dataGrid.Instance.SetSortAsync(sortByColumnName, SortDirection.Descending, x => x)); + var after = dataGrid.FindComponent>(); + + before.Should().NotBeSameAs(after, because: "If the @key is correctly set to the row item, child components will be recreated on row reordering."); + + + //Test the expanded group case + comp.SetParametersAndRender(parameters => parameters.Add(p => p.Group, true)); + await comp.InvokeAsync(() => dataGrid.Instance.ExpandAllGroups()); + + await comp.InvokeAsync(() => dataGrid.Instance.SetSortAsync(sortByColumnName, SortDirection.Ascending, x => x)); + before = dataGrid.FindComponent>(); + await comp.InvokeAsync(() => dataGrid.Instance.SetSortAsync(sortByColumnName, SortDirection.Descending, x => x)); + after = dataGrid.FindComponent>(); + + before.Should().NotBeSameAs(after, because: "If the @key is correctly set to the row item, child components will be recreated on row reordering."); } } } diff --git a/src/MudBlazor.UnitTests/Components/DatePickerTests.cs b/src/MudBlazor.UnitTests/Components/DatePickerTests.cs index e5dde9046f36..5b4dc935a78e 100644 --- a/src/MudBlazor.UnitTests/Components/DatePickerTests.cs +++ b/src/MudBlazor.UnitTests/Components/DatePickerTests.cs @@ -656,6 +656,23 @@ public async Task PersianCalendarTest_GoToDate() button.TextContent.Should().Be("1"); } + [Test] + public async Task PersianCalendarDefaultTest() + { + var timeProvider = new FakeTimeProvider(); + Context.Services.AddSingleton(timeProvider); + timeProvider.SetUtcNow(new DateTime(2025, 2, 1, 0, 0, 0, DateTimeKind.Utc)); + + var comp = Context.RenderComponent(paramter => paramter.Add(p => p.Date, null)); + var datePicker = comp.FindComponent().Instance; + await comp.InvokeAsync(() => datePicker.OpenAsync()); + + datePicker.Text.Should().BeNull(); + comp.Find("button.mud-button-year").TrimmedText().Equals("1403"); + comp.Find("button.mud-button-month").TrimmedText().Should().Contain("1403"); + comp.Find("button.mud-button-date").TrimmedText().Should().BeNullOrEmpty(); + } + [Test] public void SetPickerValue_CheckText() { diff --git a/src/MudBlazor.UnitTests/Components/ExpansionPanelTests.cs b/src/MudBlazor.UnitTests/Components/ExpansionPanelTests.cs index f0dd61642e20..1d9682ebff12 100644 --- a/src/MudBlazor.UnitTests/Components/ExpansionPanelTests.cs +++ b/src/MudBlazor.UnitTests/Components/ExpansionPanelTests.cs @@ -178,6 +178,7 @@ public async Task MudExpansionPanel_ExpandAllAync() await panels.Instance.AddPanelAsync(panel3); // We check _expandedState because we do not modify Expanded directly, therefore the parameter doesn't change. // For parameter to change you need to bind panels Expansion it via razor syntax, so that parent would update it. + panels.Instance.Panels.Count.Should().Be(3); panel1._expandedState.Value.Should().BeFalse(); panel2._expandedState.Value.Should().BeFalse(); panel3._expandedState.Value.Should().BeFalse(); @@ -205,6 +206,7 @@ public async Task MudExpansionPanel_CollapseAllAsync() await panel3.ExpandAsync(); // We check _expandedState because we do not modify Expanded directly, therefore the parameter doesn't change. // For parameter to change you need to bind panels Expansion it via razor syntax, so that parent would update it. + panels.Instance.Panels.Count.Should().Be(3); panel1._expandedState.Value.Should().BeTrue(); panel2._expandedState.Value.Should().BeTrue(); panel3._expandedState.Value.Should().BeTrue(); @@ -232,6 +234,7 @@ public async Task MudExpansionPanel_CollapseAllExceptAsync() await panel3.ExpandAsync(); // We check _expandedState because we do not modify Expanded directly, therefore the parameter doesn't change. // For parameter to change you need to bind panels Expansion it via razor syntax, so that parent would update it. + panels.Instance.Panels.Count.Should().Be(3); panel1._expandedState.Value.Should().BeTrue(); panel2._expandedState.Value.Should().BeTrue(); panel3._expandedState.Value.Should().BeTrue(); diff --git a/src/MudBlazor.UnitTests/Components/FormTests.cs b/src/MudBlazor.UnitTests/Components/FormTests.cs index eb891f353f25..33fd03bb52c8 100644 --- a/src/MudBlazor.UnitTests/Components/FormTests.cs +++ b/src/MudBlazor.UnitTests/Components/FormTests.cs @@ -4,7 +4,6 @@ using FluentAssertions; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Forms; -using MudBlazor.Docs.Examples; using MudBlazor.UnitTests.Dummy; using MudBlazor.UnitTests.TestComponents.Form; using MudBlazor.Utilities; @@ -1932,5 +1931,35 @@ public void FormSpacingClass() comp.Find("form.mud-form").ClassList.Should().Contain($"gap-{i}"); } } + + [Test] + public async Task ChildForm_TouchChangedPropogate() + { + var comp = Context.RenderComponent(); + var childFormSwitch = comp.Find(".mud-switch-input"); + var parentForm = comp.FindComponent().Instance; + var parentTextFieldCmp = comp.FindComponent>(); + var parentTextField = parentTextFieldCmp.Instance; + // display the child form + childFormSwitch.Change(true); + var forms = comp.FindComponents(); + forms.Count.Should().Be(2); + var childForm = forms[1]; + childForm.Instance.IsValid.Should().BeFalse(); + parentForm.IsValid.Should().Be(false); + + // verify they start as false + await comp.InvokeAsync(async () => await parentForm.ResetAsync()); + comp.Instance.IsParentTouchChanged.Should().BeFalse(); + comp.Instance.IsChildTouchChanged.Should().BeFalse(); + + // triggering childform touch should trigger parent form touched + var childTextFieldCmp = childForm.FindComponent>(); + childTextFieldCmp.Find("input").Change("Marilyn Manson"); + + // verify child and parent touch events happened + comp.Instance.IsParentTouchChanged.Should().BeTrue(); + comp.Instance.IsChildTouchChanged.Should().BeTrue(); + } } } diff --git a/src/MudBlazor.UnitTests/Components/NumericFieldTests.cs b/src/MudBlazor.UnitTests/Components/NumericFieldTests.cs index d5f00c6066ad..890a190f2af2 100644 --- a/src/MudBlazor.UnitTests/Components/NumericFieldTests.cs +++ b/src/MudBlazor.UnitTests/Components/NumericFieldTests.cs @@ -1019,6 +1019,36 @@ public void Should_ignore_default_culture() comp.Instance.NumericField.Culture.Name.Should().Be(""); } + [Test] + [SetUICulture("ru-RU")] + public void Format_should_use_default_culture() + { + var comp = Context.RenderComponent>(parameters => parameters + .Add(p => p.Format, "N3")); + + comp.Find("input").Change("123,45"); + comp.Find("input").Blur(); + + comp.WaitForAssertion(() => comp.Instance.Value.Should().Be(123.45M)); + comp.Instance.Text.Should().Be("123,450"); + comp.Instance.Culture.Name.Should().Be("ru-RU"); + } + + [Test] + [SetUICulture("ru-RU")] + public void Pattern_should_use_default_culture() + { + var comp = Context.RenderComponent>(parameters => parameters + .Add(p => p.Pattern, "[0-9,.\\-]")); + + comp.Find("input").Change("123,45"); + comp.Find("input").Blur(); + + comp.WaitForAssertion(() => comp.Instance.Value.Should().Be(123.45M)); + comp.Instance.Text.Should().Be("123,45"); + comp.Instance.Culture.Name.Should().Be("ru-RU"); + } + [Test] [SetUICulture("ru-RU")] public void Should_apply_defined_culture() @@ -1043,6 +1073,38 @@ public void Should_apply_defined_culture() comp.Instance.FieldImmediate.Culture.Name.Should().Be("en-US"); } + [Test] + [SetUICulture("ru-RU")] + public void Format_should_use_defined_culture() + { + var comp = Context.RenderComponent>(parameters => parameters + .Add(p => p.Format, "N3") + .Add(p => p.Culture, CultureInfo.GetCultureInfo("en-US"))); + + comp.Find("input").Change("123.45"); + comp.Find("input").Blur(); + + comp.WaitForAssertion(() => comp.Instance.Value.Should().Be(123.45M)); + comp.Instance.Text.Should().Be("123.450"); + comp.Instance.Culture.Name.Should().Be("en-US"); + } + + [Test] + [SetUICulture("ru-RU")] + public void Pattern_should_use_defined_culture() + { + var comp = Context.RenderComponent>(parameters => parameters + .Add(p => p.Pattern, "[0-9,.\\-]") + .Add(p => p.Culture, CultureInfo.GetCultureInfo("en-US"))); + + comp.Find("input").Change("123.45"); + comp.Find("input").Blur(); + + comp.WaitForAssertion(() => comp.Instance.Value.Should().Be(123.45M)); + comp.Instance.Text.Should().Be("123.45"); + comp.Instance.Culture.Name.Should().Be("en-US"); + } + [Test] public void Should_render_conversion_error_message() { diff --git a/src/MudBlazor.UnitTests/Components/SelectTests.cs b/src/MudBlazor.UnitTests/Components/SelectTests.cs index 2d9be0738156..f81f7d972d04 100644 --- a/src/MudBlazor.UnitTests/Components/SelectTests.cs +++ b/src/MudBlazor.UnitTests/Components/SelectTests.cs @@ -1217,6 +1217,91 @@ public async Task MultiSelectWithRequiredValue() selectWithT.ValidationErrors.Count.Should().Be(0); } + [Test] + public async Task MultiSelectClearAndReset() + { + var comp = Context.RenderComponent(); + var select = comp.FindComponent>().Instance; + select.Required.Should().BeTrue(); + await comp.InvokeAsync(() => select.Validate()); + select.ValidationErrors.First().Should().Be("Required"); + + await comp.Find("#clear-string").ClickAsync(new MouseEventArgs()); + select.ValidationErrors.First().Should().Be("Required"); + + await comp.Find("#reset-string").ClickAsync(new MouseEventArgs()); + select.ValidationErrors.Should().BeEmpty(); + + //test clearing string values + var inputs = comp.FindAll("div.mud-input-control"); + await inputs[0].MouseDownAsync(new MouseEventArgs()); + + var items = comp.FindAll("div.mud-list-item").ToArray(); + await items[1].ClickAsync(new MouseEventArgs()); + await inputs[0].MouseDownAsync(new MouseEventArgs()); + select.Value.Should().Be("2"); + select.SelectedValues.Should().Contain("2"); + + await comp.Find("#clear-string").ClickAsync(new MouseEventArgs()); + + select.Value.Should().BeNullOrEmpty(); + select.SelectedValues.Should().BeEmpty(); + select.ValidationErrors.First().Should().Be("Required"); + + //test resetting string values + inputs = comp.FindAll("div.mud-input-control"); + await inputs[0].MouseDownAsync(new MouseEventArgs()); + items = comp.FindAll("div.mud-list-item").ToArray(); + await items[1].ClickAsync(new MouseEventArgs()); + await inputs[0].MouseDownAsync(new MouseEventArgs()); + select.Value.Should().Be("2"); + select.SelectedValues.Should().Contain("2"); + + await comp.Find("#reset-string").ClickAsync(new MouseEventArgs()); + + select.Value.Should().BeNullOrEmpty(); + select.SelectedValues.Should().BeEmpty(); + select.ValidationErrors.Should().BeEmpty(); + + //test clearing object values + var select2 = comp.FindComponent>().Instance; + select2.Required.Should().BeTrue(); + await comp.InvokeAsync(() => select2.Validate()); + select2.ValidationErrors.First().Should().Be("Required"); + + await comp.Find("#clear-object").ClickAsync(new MouseEventArgs()); + select2.ValidationErrors.First().Should().Be("Required"); + + await comp.Find("#reset-object").ClickAsync(new MouseEventArgs()); + select2.ValidationErrors.Should().BeEmpty(); + + inputs = comp.FindAll("div.mud-input-control"); + await inputs[1].MouseDownAsync(new MouseEventArgs()); + + items = comp.FindAll("div.mud-list-item").ToArray(); + await items[1].ClickAsync(new MouseEventArgs()); + await inputs[1].MouseDownAsync(new MouseEventArgs()); + select2.SelectedValues.Select(x => x.Name).Should().Contain("Customer"); + + await comp.Find("#clear-object").ClickAsync(new MouseEventArgs()); + + select2.SelectedValues.Should().BeEmpty(); + select2.ValidationErrors.First().Should().Be("Required"); + + //test resetting object values + inputs = comp.FindAll("div.mud-input-control"); + await inputs[1].MouseDownAsync(new MouseEventArgs()); + items = comp.FindAll("div.mud-list-item").ToArray(); + await items[1].ClickAsync(new MouseEventArgs()); + await inputs[1].MouseDownAsync(new MouseEventArgs()); + select2.SelectedValues.Select(x => x.Name).Should().Contain("Customer"); + + await comp.Find("#reset-object").ClickAsync(new MouseEventArgs()); + + select2.SelectedValues.Should().BeEmpty(); + select2.ValidationErrors.Should().BeEmpty(); + } + /// /// When MultiSelect attribute goes after SelectedValues, text should contain all selected values. /// diff --git a/src/MudBlazor.UnitTests/Components/StackTests.cs b/src/MudBlazor.UnitTests/Components/StackTests.cs index b39a422f3a18..d8ce2594e5e8 100644 --- a/src/MudBlazor.UnitTests/Components/StackTests.cs +++ b/src/MudBlazor.UnitTests/Components/StackTests.cs @@ -82,6 +82,124 @@ public void CheckSpacingClass(int spacing) stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", "flex-column", $"gap-{spacing}" }); } + [Test] + [TestCase(Breakpoint.None)] + [TestCase(Breakpoint.Always)] + [TestCase(Breakpoint.Xs)] + [TestCase(Breakpoint.Sm)] + [TestCase(Breakpoint.Md)] + [TestCase(Breakpoint.Lg)] + [TestCase(Breakpoint.Xl)] + [TestCase(Breakpoint.Xxl)] + [TestCase(Breakpoint.SmAndDown)] + [TestCase(Breakpoint.MdAndDown)] + [TestCase(Breakpoint.LgAndDown)] + [TestCase(Breakpoint.XlAndDown)] + [TestCase(Breakpoint.SmAndUp)] + [TestCase(Breakpoint.MdAndUp)] + [TestCase(Breakpoint.LgAndUp)] + [TestCase(Breakpoint.XlAndUp)] + [TestCase(Breakpoint.None, true)] + [TestCase(Breakpoint.Always, true)] + [TestCase(Breakpoint.Xs, true)] + [TestCase(Breakpoint.Sm, true)] + [TestCase(Breakpoint.Md, true)] + [TestCase(Breakpoint.Lg, true)] + [TestCase(Breakpoint.Xl, true)] + [TestCase(Breakpoint.Xxl, true)] + [TestCase(Breakpoint.SmAndDown, true)] + [TestCase(Breakpoint.MdAndDown, true)] + [TestCase(Breakpoint.LgAndDown, true)] + [TestCase(Breakpoint.XlAndDown, true)] + [TestCase(Breakpoint.SmAndUp, true)] + [TestCase(Breakpoint.MdAndUp, true)] + [TestCase(Breakpoint.LgAndUp, true)] + [TestCase(Breakpoint.XlAndUp, true)] + [TestCase(Breakpoint.None, true, true)] + [TestCase(Breakpoint.Always, true, true)] + [TestCase(Breakpoint.Xs, true, true)] + [TestCase(Breakpoint.Sm, true, true)] + [TestCase(Breakpoint.Md, true, true)] + [TestCase(Breakpoint.Lg, true, true)] + [TestCase(Breakpoint.Xl, true, true)] + [TestCase(Breakpoint.Xxl, true, true)] + [TestCase(Breakpoint.SmAndDown, true, true)] + [TestCase(Breakpoint.MdAndDown, true, true)] + [TestCase(Breakpoint.LgAndDown, true, true)] + [TestCase(Breakpoint.XlAndDown, true, true)] + [TestCase(Breakpoint.SmAndUp, true, true)] + [TestCase(Breakpoint.MdAndUp, true, true)] + [TestCase(Breakpoint.LgAndUp, true, true)] + [TestCase(Breakpoint.XlAndUp, true, true)] + public void CheckBreakpointClass(Breakpoint breakpoint, bool row = false, bool reverse = false) + { + var stack = Context.RenderComponent(x => x.Add(c => c.Breakpoint, breakpoint).Add(c => c.Row, row).Add(c => c.Reverse, reverse)); + + // Get the Default and Reverse States + string defaultState = (row ? "row" : "column") + (reverse ? "-reverse" : string.Empty); + string reverseState = (row ? "column" : "row") + (reverse ? "-reverse" : string.Empty); + + // Get the Stack Class + var stackClass = stack.Find(".d-flex"); + + // Handle Special Cases + switch (breakpoint) + { + // If the Breakpoint is None or Always, return the default direction + case Breakpoint.None: // If breakpoint is None, return the default direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{defaultState}", "gap-3" }); + break; + case Breakpoint.Always: // If breakpoint is Always, return the reverse direction, honestly the user should just use the Row Property + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{reverseState}", "gap-3" }); + break; + case Breakpoint.Xs: // Xs is Reverse Direction, Sm and Up is Default Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{reverseState}", $"flex-sm-{defaultState}", "gap-3" }); + break; + case Breakpoint.Sm: // Xs is Default Direction, Sm is Reverse Direction, Md and Up is Default Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{defaultState}", $"flex-sm-{reverseState}", $"flex-md-{defaultState}", "gap-3" }); + break; + case Breakpoint.Md: // Xs to Sm is Default Direction, Md is Reverse Direction, Lg and Up is Default Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{defaultState}", $"flex-md-{reverseState}", $"flex-lg-{defaultState}", "gap-3" }); + break; + case Breakpoint.Lg: // Xs to Md is Default Direction, Lg is Reverse Direction, Xl and Up is Default Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{defaultState}", $"flex-lg-{reverseState}", $"flex-xl-{defaultState}", "gap-3" }); + break; + case Breakpoint.Xl: // Xs to Lg is Default Direction, Xl is Reverse Direction, Xxl is Default Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{defaultState}", $"flex-xl-{reverseState}", $"flex-xxl-{defaultState}", "gap-3" }); + break; + case Breakpoint.Xxl: // Xs to Xl is Default Direction, Xxl is Reverse Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{defaultState}", $"flex-xxl-{reverseState}", "gap-3" }); + break; + case Breakpoint.SmAndDown: // Sm and Down is Reverse Direction, Md and Up is Default Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{reverseState}", $"flex-md-{defaultState}", "gap-3" }); + break; + case Breakpoint.MdAndDown: // Md and Down is Reverse Direction, Lg and Up is Default Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{reverseState}", $"flex-lg-{defaultState}", "gap-3" }); + break; + case Breakpoint.LgAndDown: // Lg and Down is Reverse Direction, Xl and Up is Default Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{reverseState}", $"flex-xl-{defaultState}", "gap-3" }); + break; + case Breakpoint.XlAndDown: // Xl and Down is Reverse Direction, Xxl and Up is Default Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{reverseState}", $"flex-xxl-{defaultState}", "gap-3" }); + break; + case Breakpoint.SmAndUp: // Xs is Default Direction, Sm and Up is Reverse Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{defaultState}", $"flex-sm-{reverseState}", "gap-3" }); + break; + case Breakpoint.MdAndUp: // Xs to Sm is Default Direction, Md and Up is Reverse Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{defaultState}", $"flex-md-{reverseState}", "gap-3" }); + break; + case Breakpoint.LgAndUp: // Xs to Md is Default Direction, Lg and Up is Reverse Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{defaultState}", $"flex-lg-{reverseState}", "gap-3" }); + break; + case Breakpoint.XlAndUp: // Xs to Lg is Default Direction, Xl and Up is Reverse Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{defaultState}", $"flex-xl-{reverseState}", "gap-3" }); + break; + default: // Return the default direction if no Breakpoint is Matched + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{defaultState}", "gap-3" }); + break; + } + } + [Test] [TestCase(Justify.FlexStart, "start")] [TestCase(Justify.Center, "center")] diff --git a/src/MudBlazor.UnitTests/Components/TableTests.cs b/src/MudBlazor.UnitTests/Components/TableTests.cs index 853a4c4aae2e..de3b20cb4e09 100644 --- a/src/MudBlazor.UnitTests/Components/TableTests.cs +++ b/src/MudBlazor.UnitTests/Components/TableTests.cs @@ -115,6 +115,24 @@ 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. /// @@ -1449,15 +1467,15 @@ public void TableRowClassStyleTest() tds[2].TextContent.Trim().Should().Be("2"); tds[3].TextContent.Trim().Should().Be("3"); - trs[1].GetAttribute("style").Contains("color: red"); - trs[2].GetAttribute("style").Contains("color: red"); - trs[3].GetAttribute("style").Contains("color: blue"); - trs[4].GetAttribute("style").Contains("color: blue"); + trs[1].GetAttribute("style").Should().Contain("color: red"); + trs[2].GetAttribute("style").Should().Contain("color: red"); + trs[3].GetAttribute("style").Should().Contain("color: blue"); + trs[4].GetAttribute("style").Should().Contain("color: blue"); - trs[1].GetAttribute("class").Contains("even"); - trs[2].GetAttribute("class").Contains("odd"); - trs[3].GetAttribute("class").Contains("even"); - trs[4].GetAttribute("class").Contains("odd"); + trs[1].GetAttribute("class").Should().Contain("even"); + trs[2].GetAttribute("class").Should().Contain("odd"); + trs[3].GetAttribute("class").Should().Contain("even"); + trs[4].GetAttribute("class").Should().Contain("odd"); } public class TableRowValidatorTest : TableRowValidator diff --git a/src/MudBlazor.UnitTests/Components/TabsTests.cs b/src/MudBlazor.UnitTests/Components/TabsTests.cs index 3956855f1a54..93c255908181 100644 --- a/src/MudBlazor.UnitTests/Components/TabsTests.cs +++ b/src/MudBlazor.UnitTests/Components/TabsTests.cs @@ -1318,5 +1318,22 @@ public void Tabs_HaveRipple_WhenRippleIsTrue() comp.SetParametersAndRender(parameters => parameters.Add(p => p.Ripple, false)); comp.FindAll("div.mud-ripple").Count.Should().Be(0); } + + [TestCase(true)] + [TestCase(false)] + public void TabPanel_Hidden_Class(bool visible) + { + var comp = Context.RenderComponent(parameters => parameters.Add(x => x.Visible, visible)); + + var panel = comp.FindAll(".mud-tab-panel")[1]; + if (visible) + { + panel.ClassList.Should().NotContain("mud-tab-panel-hidden"); + } + else + { + panel.ClassList.Should().Contain("mud-tab-panel-hidden"); + } + } } } diff --git a/src/MudBlazor.UnitTests/MudBlazor.UnitTests.csproj b/src/MudBlazor.UnitTests/MudBlazor.UnitTests.csproj index f94a110284db..31c5f753ce41 100644 --- a/src/MudBlazor.UnitTests/MudBlazor.UnitTests.csproj +++ b/src/MudBlazor.UnitTests/MudBlazor.UnitTests.csproj @@ -20,15 +20,15 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all
- + - - - + + + diff --git a/src/MudBlazor.UnitTests/Utilities/Debounce/DebounceDispatcherTests.cs b/src/MudBlazor.UnitTests/Utilities/Debounce/DebounceDispatcherTests.cs index 094f1caea640..e274c65ce901 100644 --- a/src/MudBlazor.UnitTests/Utilities/Debounce/DebounceDispatcherTests.cs +++ b/src/MudBlazor.UnitTests/Utilities/Debounce/DebounceDispatcherTests.cs @@ -56,7 +56,7 @@ async Task CallDebounceAsyncAfterDelay(int delay) { await Task.Delay(delay).ConfigureAwait(false); await debounceDispatcher.DebounceAsync(Invoke); - }; + } // Act var tasks = new[] diff --git a/src/MudBlazor.UnitTests/Utilities/ObserverManager/ObserverManagerTests.cs b/src/MudBlazor.UnitTests/Utilities/ObserverManager/ObserverManagerTests.cs index aa54d23b328f..496dd93901cd 100644 --- a/src/MudBlazor.UnitTests/Utilities/ObserverManager/ObserverManagerTests.cs +++ b/src/MudBlazor.UnitTests/Utilities/ObserverManager/ObserverManagerTests.cs @@ -35,6 +35,55 @@ public void Constructor_ThrowsException() construct.Should().Throw(); } + [Test] + public void TryGetSubscription_ReturnsTrueAndObserver_WhenObserverExists() + { + // Arrange + var id = 1; + var observer = "Observer1"; + _observerManager.Subscribe(id, observer); + + // Act + var result = _observerManager.TryGetSubscription(id, out var retrievedObserver); + + // Assert + result.Should().BeTrue(); + retrievedObserver.Should().Be(observer); + } + + [Test] + public void TryGetSubscription_ReturnsFalseAndDefault_WhenObserverDoesNotExist() + { + // Arrange + var id = 1; + + // Act + var result = _observerManager.TryGetSubscription(id, out var retrievedObserver); + + // Assert + result.Should().BeFalse(); + retrievedObserver.Should().BeNull(); + } + + [Test] + public void FindObserverIdentities_ReturnsMatchingIdentities() + { + // Arrange + var observer1 = "Observer1"; + var observer2 = "Observer2"; + var observer3 = "Observer3"; + + _observerManager.Subscribe(1, observer1); + _observerManager.Subscribe(2, observer2); + _observerManager.Subscribe(3, observer3); + + // Act + var result = _observerManager.FindObserverIdentities((_, observer) => observer.Contains('2')).ToList(); + + // Assert + result.Should().ContainSingle().Which.Should().Be(2); + } + [Test] public void Subscribe_AddsObserverToDictionary() { @@ -80,7 +129,7 @@ public void Unsubscribe_RemovesObserverFromDictionary() // Assert _observerManager.Count.Should().Be(0); - _observerManager.Observers.ContainsKey(id).Should().BeFalse(); + _observerManager.IsSubscribed(id).Should().BeFalse(); } [Test] @@ -134,9 +183,9 @@ Task NotificationAsync(string observer) // Assert _observerManager.Count.Should().Be(2); - _observerManager.Observers.ContainsKey(1).Should().BeTrue(); - _observerManager.Observers.ContainsKey(3).Should().BeTrue(); - _observerManager.Observers.ContainsKey(2).Should().BeFalse(); + _observerManager.IsSubscribed(1).Should().BeTrue(); + _observerManager.IsSubscribed(3).Should().BeTrue(); + _observerManager.IsSubscribed(2).Should().BeFalse(); } [Test] diff --git a/src/MudBlazor/Components/Autocomplete/MudAutocomplete.razor b/src/MudBlazor/Components/Autocomplete/MudAutocomplete.razor index ab8509fbd9e4..02f53e307b9b 100644 --- a/src/MudBlazor/Components/Autocomplete/MudAutocomplete.razor +++ b/src/MudBlazor/Components/Autocomplete/MudAutocomplete.razor @@ -1,4 +1,4 @@ -@namespace MudBlazor +@namespace MudBlazor @inherits MudBaseInput @typeparam T @@ -44,7 +44,7 @@ MaxLength="@MaxLength" autocomplete="@GetAutocomplete()" @attributes="UserAttributes" - TextChanged="OnTextChangedAsync" + TextChanged="OnTextChangedAsync" @onfocus="@OnInputFocusedAsync" OnBlur="OnInputBlurredAsync" OnKeyDown="@OnInputKeyDownAsync" diff --git a/src/MudBlazor/Components/Autocomplete/MudAutocomplete.razor.cs b/src/MudBlazor/Components/Autocomplete/MudAutocomplete.razor.cs index 20c4bbd00b53..d32ae8e2ec22 100644 --- a/src/MudBlazor/Components/Autocomplete/MudAutocomplete.razor.cs +++ b/src/MudBlazor/Components/Autocomplete/MudAutocomplete.razor.cs @@ -998,7 +998,7 @@ internal async Task AdornmentClickHandlerAsync() { if (OnAdornmentClick.HasDelegate) { - await FocusAsync(); + await OnAdornmentClick.InvokeAsync(); } else diff --git a/src/MudBlazor/Components/Chart/Charts/Donut.razor b/src/MudBlazor/Components/Chart/Charts/Donut.razor index 14166d4aeb17..cb8959b17677 100644 --- a/src/MudBlazor/Components/Chart/Charts/Donut.razor +++ b/src/MudBlazor/Components/Chart/Charts/Donut.razor @@ -1,9 +1,10 @@ @namespace MudBlazor.Charts +@using System.Globalization @inherits MudCategoryChartBase - + @foreach (var item in _circles) { } - - @foreach (var o in operators) - { - if (!string.IsNullOrWhiteSpace(o)) + @if (!Column.ShowFilterIcon.HasValue || Column.ShowFilterIcon.Value) + { + + @foreach (var o in operators) { - @Localizer[FilterOperator.GetTranslationKeyByOperatorName(o)] + if (!string.IsNullOrWhiteSpace(o)) + { + @Localizer[FilterOperator.GetTranslationKeyByOperatorName(o)] + } } - } - - + + + } } diff --git a/src/MudBlazor/Components/DataGrid/MudDataGrid.razor b/src/MudBlazor/Components/DataGrid/MudDataGrid.razor index e39aee5d5a88..adb74c6285e4 100644 --- a/src/MudBlazor/Components/DataGrid/MudDataGrid.razor +++ b/src/MudBlazor/Components/DataGrid/MudDataGrid.razor @@ -269,7 +269,7 @@ @{ var rowClass = new CssBuilder(RowClass).AddClass(RowClassFunc?.Invoke(itemBag.Item, itemBag.Index)).Build(); } @{ var rowStyle = new StyleBuilder().AddStyle(RowStyle).AddStyle(RowStyleFunc?.Invoke(itemBag.Item, itemBag.Index)).Build(); } -
@@ -330,7 +330,7 @@ @{ var rowClass = new CssBuilder(RowClass).AddClass(RowClassFunc?.Invoke(itemBag.Item, itemBag.Index)).Build(); } @{ var rowStyle = new StyleBuilder().AddStyle(RowStyle).AddStyle(RowStyleFunc?.Invoke(itemBag.Item, itemBag.Index)).Build(); } -
diff --git a/src/MudBlazor/Components/DatePicker/MudBaseDatePicker.razor.cs b/src/MudBlazor/Components/DatePicker/MudBaseDatePicker.razor.cs index 04f3118fcc23..c8b5b70fe7a5 100644 --- a/src/MudBlazor/Components/DatePicker/MudBaseDatePicker.razor.cs +++ b/src/MudBlazor/Components/DatePicker/MudBaseDatePicker.razor.cs @@ -526,7 +526,7 @@ protected string GetFormattedYearString() { var selectedYear = HighlightedDate ?? GetMonthStart(0); - return selectedYear.ToString("yyyy", Culture); + return GetCalendarYear(selectedYear).ToString(); } private void OnPreviousMonthClick() @@ -697,7 +697,7 @@ protected override void OnInitialized() var month = FixMonth ?? (year == today.Year ? today.Month : 1); var day = FixDay ?? 1; - if (DateTime.TryParseExact($"{year}-{month}-{day}", "yyyy-M-d", Culture, DateTimeStyles.None, out var date)) + if (DateTime.TryParseExact($"{year}-{month}-{day}", "yyyy-M-d", CultureInfo.InvariantCulture, DateTimeStyles.None, out var date)) { HighlightedDate = date; } diff --git a/src/MudBlazor/Components/DropZone/MudDynamicDropItem.razor b/src/MudBlazor/Components/DropZone/MudDynamicDropItem.razor index a3bbcbcf4715..e02d9ef3acb4 100644 --- a/src/MudBlazor/Components/DropZone/MudDynamicDropItem.razor +++ b/src/MudBlazor/Components/DropZone/MudDynamicDropItem.razor @@ -2,7 +2,7 @@ @inherits MudComponentBase @typeparam T -
+ new StyleBuilder() + .AddStyle("touch-action", "none", !Disabled) + .AddStyle("transform", "translate3d(0px, 0px, 0px)") + .AddStyle(Style) + .Build(); } diff --git a/src/MudBlazor/Components/ExpansionPanel/MudExpansionPanels.razor.cs b/src/MudBlazor/Components/ExpansionPanel/MudExpansionPanels.razor.cs index 592d0f133fe6..c4e7eaed0728 100644 --- a/src/MudBlazor/Components/ExpansionPanel/MudExpansionPanels.razor.cs +++ b/src/MudBlazor/Components/ExpansionPanel/MudExpansionPanels.razor.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components; using MudBlazor.Utilities; namespace MudBlazor @@ -91,6 +87,14 @@ public partial class MudExpansionPanels : MudComponentBase [Category(CategoryTypes.ExpansionPanel.Behavior)] public RenderFragment? ChildContent { get; set; } + /// + /// A read-only list of the panels within this component. + /// + /// + /// Expansion panels are controlled by adding more components in the Razor page. + /// + public IReadOnlyList Panels => _panels; + internal async Task AddPanelAsync(MudExpansionPanel panel) { if (!MultiExpansion && _panels.Any(p => p._expandedState.Value)) @@ -148,15 +152,11 @@ public Task UpdateAllAsync() /// The panel to keep expanded. public async Task CollapseAllExceptAsync(MudExpansionPanel panel) { - foreach (var expansionPanel in _panels) + foreach (var expansionPanel in _panels.Where(expansionPanel => expansionPanel != panel)) { - if (expansionPanel == panel) - { - continue; - } - await expansionPanel.CollapseAsync(); } + await InvokeAsync(UpdateAllAsync); } diff --git a/src/MudBlazor/Components/Form/MudForm.razor.cs b/src/MudBlazor/Components/Form/MudForm.razor.cs index b84cf098a2a5..35cd9ec763da 100644 --- a/src/MudBlazor/Components/Form/MudForm.razor.cs +++ b/src/MudBlazor/Components/Form/MudForm.razor.cs @@ -241,7 +241,13 @@ protected async Task OnEvaluateForm() SetIsValid(valid); await ErrorsChanged.InvokeAsync(Errors); if (oldTouched != _touched) + { await IsTouchedChanged.InvokeAsync(_touched); + if (ParentMudForm != null) + { + await ParentMudForm.IsTouchedChanged.InvokeAsync(_touched); + } + } } finally { diff --git a/src/MudBlazor/Components/NumericField/MudNumericField.razor.cs b/src/MudBlazor/Components/NumericField/MudNumericField.razor.cs index d314d36bbd29..f06ee2ab0a46 100644 --- a/src/MudBlazor/Components/NumericField/MudNumericField.razor.cs +++ b/src/MudBlazor/Components/NumericField/MudNumericField.razor.cs @@ -46,14 +46,6 @@ public MudNumericField() .WithParameter(() => Culture) .WithChangeHandler((x) => _cultureHasValue = x.Value is not null); - // Overrides the browser's culture since does not consider culture. - // If a specific Culture, Pattern, or Format is defined, will be used - // with the corresponding attributes applied. - if (!_cultureHasValue) - { - SetCulture(CultureInfo.InvariantCulture); - } - Validation = new Func>(ValidateInput); #region parameters default depending on T @@ -150,6 +142,24 @@ public MudNumericField() private bool IsNumberMode => InputMode == InputMode.numeric || InputMode == InputMode.@decimal; private bool IsFormatted => Pattern is not null || Format is not null || _cultureHasValue; + protected override void OnAfterRender(bool firstRender) + { + base.OnAfterRender(firstRender); + + if (!firstRender) + { + return; + } + + // Overrides the browser's culture since does not consider culture. + // If a specific Culture, Pattern, or Format is defined, will be used + // with the corresponding attributes applied. + if (!IsFormatted) + { + SetCulture(CultureInfo.InvariantCulture); + } + } + /// [ExcludeFromCodeCoverage] public override ValueTask FocusAsync() diff --git a/src/MudBlazor/Components/Select/MudSelect.razor.cs b/src/MudBlazor/Components/Select/MudSelect.razor.cs index af7f8a4cfcae..69cce08afebe 100644 --- a/src/MudBlazor/Components/Select/MudSelect.razor.cs +++ b/src/MudBlazor/Components/Select/MudSelect.razor.cs @@ -2,6 +2,7 @@ // MudBlazor licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; using MudBlazor.Services; @@ -1189,10 +1190,25 @@ internal Task HandleKeyUpAsync(KeyboardEventArgs obj) return OnKeyUp.InvokeAsync(obj); } + /// + /// Clears all selections and resets validation + /// + /// + /// To maintain validation errors (e.g. required), use + /// + protected override async Task ResetValueAsync() + { + await ClearAsync(); + await base.ResetValueAsync(); + } + /// /// Clears all selections. /// - public async Task Clear() + /// + /// To reset validation errors (e.g. required), use + /// + public async Task ClearAsync() { await SetValueAsync(default, false); await SetTextAsync(default, false); @@ -1202,6 +1218,16 @@ public async Task Clear() await SelectedValuesChanged.InvokeAsync(_selectedValues); } + /// + /// Clears all selections. + /// + /// + /// To reset validation errors (e.g. required), use + /// + [ExcludeFromCodeCoverage] + [Obsolete("Use ClearAsync instead")] + public Task Clear() => ClearAsync(); + private async Task SelectAllClickAsync() { // Manage the fake tri-state of a checkbox @@ -1215,7 +1241,7 @@ private async Task SelectAllClickAsync() if (_selectAllChecked.Value) await SelectAllItems(); else - await Clear(); + await ClearAsync(); } private async Task SelectAllItems() diff --git a/src/MudBlazor/Components/Stack/MudStack.razor.cs b/src/MudBlazor/Components/Stack/MudStack.razor.cs index 32157b89f4f4..20dd1c9961b4 100644 --- a/src/MudBlazor/Components/Stack/MudStack.razor.cs +++ b/src/MudBlazor/Components/Stack/MudStack.razor.cs @@ -16,7 +16,7 @@ public partial class MudStack : MudComponentBase { protected string Classname => new CssBuilder("d-flex") - .AddClass($"flex-{(Row ? "row" : "column")}{(Reverse ? "-reverse" : string.Empty)}") + .AddClass(getFlexDirection()) .AddClass($"justify-{Justify?.ToDescriptionString()}", Justify is not null) .AddClass($"align-{AlignItems?.ToDescriptionString()}", AlignItems is not null) .AddClass($"flex-{Wrap?.ToDescriptionString()}", Wrap is not null) @@ -25,6 +25,67 @@ public partial class MudStack : MudComponentBase .AddClass(Class) .Build(); + /// + /// Gets the CSS flex direction based on the current breakpoint and row settings. + /// + /// + /// This property generates a CSS flex direction string based on the following conditions: + /// - If the breakpoint is not set or is "none" or "always", it returns the default direction based on the Row and Reverse properties. + /// - If the breakpoint is one of the predefined sizes ("xs", "sm", "md", "lg", "xl", "xxl"), it generates a flex direction string that changes at the specified breakpoint. + /// - If the breakpoint ends with "anddown", it generates a flex direction string that applies up to and including the specified breakpoint. + /// - If the breakpoint ends with "andup", it generates a flex direction string that applies from the specified breakpoint and up. + /// + /// + /// A string representing the CSS flex direction based on the current breakpoint and row settings. + /// + private string getFlexDirection() + { + // Sets the inital direction based on the Row Parameter and Appends the reverse if needed + string defaultState = (Row ? "row" : "column") + (Reverse ? "-reverse" : string.Empty); + // Sets the reverse direction based on the Row Parameter and Appends the reverse if needed + string reverseState = (Row ? "column" : "row") + (Reverse ? "-reverse" : string.Empty); + + // Switch statement to determine the breakpoint and return the appropriate flex direction + switch (Breakpoint) + { + // If the Breakpoint is None or Always, return the default direction + case Breakpoint.None: // If breakpoint is None, return the default direction + return $"flex-{defaultState}"; + case Breakpoint.Always: // If breakpoint is Always, return the reverse direction, honestly the user should just use the Row Property + return $"flex-{reverseState}"; + case Breakpoint.Xs: // Xs is Reverse Direction, Sm and Up is Default Direction + return $"flex-{reverseState} flex-sm-{defaultState}"; + case Breakpoint.Sm: // Xs is Default Direction, Sm is Reverse Direction, Md and Up is Default Direction + return $"flex-{defaultState} flex-sm-{reverseState} flex-md-{defaultState}"; + case Breakpoint.Md: // Xs to Sm is Default Direction, Md is Reverse Direction, Lg and Up is Default Direction + return $"flex-{defaultState} flex-md-{reverseState} flex-lg-{defaultState}"; + case Breakpoint.Lg: // Xs to Md is Default Direction, Lg is Reverse Direction, Xl and Up is Default Direction + return $"flex-{defaultState} flex-lg-{reverseState} flex-xl-{defaultState}"; + case Breakpoint.Xl: // Xs to Lg is Default Direction, Xl is Reverse Direction, Xxl is Default Direction + return $"flex-{defaultState} flex-xl-{reverseState} flex-xxl-{defaultState}"; + case Breakpoint.Xxl: // Xs to Xl is Default Direction, Xxl is Reverse Direction + return $"flex-{defaultState} flex-xxl-{reverseState}"; + case Breakpoint.SmAndDown: // Sm and Down is Reverse Direction, Md and Up is Default Direction + return $"flex-{reverseState} flex-md-{defaultState}"; + case Breakpoint.MdAndDown: // Md and Down is Reverse Direction, Lg and Up is Default Direction + return $"flex-{reverseState} flex-lg-{defaultState}"; + case Breakpoint.LgAndDown: // Lg and Down is Reverse Direction, Xl and Up is Default Direction + return $"flex-{reverseState} flex-xl-{defaultState}"; + case Breakpoint.XlAndDown: // Xl and Down is Reverse Direction, Xxl and Up is Default Direction + return $"flex-{reverseState} flex-xxl-{defaultState}"; + case Breakpoint.SmAndUp: // Xs is Default Direction, Sm and Up is Reverse Direction + return $"flex-{defaultState} flex-sm-{reverseState}"; + case Breakpoint.MdAndUp: // Xs to Sm is Default Direction, Md and Up is Reverse Direction + return $"flex-{defaultState} flex-md-{reverseState}"; + case Breakpoint.LgAndUp: // Xs to Md is Default Direction, Lg and Up is Reverse Direction + return $"flex-{defaultState} flex-lg-{reverseState}"; + case Breakpoint.XlAndUp: // Xs to Lg is Default Direction, Xl and Up is Reverse Direction + return $"flex-{defaultState} flex-xl-{reverseState}"; + default: // Return the default direction if no Breakpoint is Matched + return $"flex-{defaultState}"; + } + } + /// /// Displays items horizontally. /// @@ -98,6 +159,13 @@ public partial class MudStack : MudComponentBase [Category(CategoryTypes.Stack.Behavior)] public Wrap? Wrap { get; set; } + /// + /// The breakpoint at which the Stack should switch to a row or column layout. + /// + [Parameter] + [Category(CategoryTypes.Stack.Behavior)] + public Breakpoint Breakpoint { get; set; } = Breakpoint.None; + /// /// The content within this component. /// diff --git a/src/MudBlazor/Components/Table/MudTableSortLabel.razor.cs b/src/MudBlazor/Components/Table/MudTableSortLabel.razor.cs index 2d3cd7be4a9d..5023c5de88f0 100644 --- a/src/MudBlazor/Components/Table/MudTableSortLabel.razor.cs +++ b/src/MudBlazor/Components/Table/MudTableSortLabel.razor.cs @@ -16,7 +16,8 @@ public partial class MudTableSortLabel<[DynamicallyAccessedMembers(DynamicallyAc { private SortDirection _direction = SortDirection.None; - protected string Classname => new CssBuilder("mud-button-root mud-table-sort-label") + protected string Classname => new CssBuilder("mud-table-sort-label") + .AddClass("mud-button-root", Enabled) .AddClass(Class) .Build(); diff --git a/src/MudBlazor/Components/Tabs/MudTabPanel.razor b/src/MudBlazor/Components/Tabs/MudTabPanel.razor index c8dbc27560ce..9199fdc60ec7 100644 --- a/src/MudBlazor/Components/Tabs/MudTabPanel.razor +++ b/src/MudBlazor/Components/Tabs/MudTabPanel.razor @@ -4,7 +4,7 @@ @if (Parent?.KeepPanelsAlive == true) { -
+
@ChildContent
} diff --git a/src/MudBlazor/Components/Tabs/MudTabPanel.razor.cs b/src/MudBlazor/Components/Tabs/MudTabPanel.razor.cs index ec74173261f9..b88130a57a9e 100644 --- a/src/MudBlazor/Components/Tabs/MudTabPanel.razor.cs +++ b/src/MudBlazor/Components/Tabs/MudTabPanel.razor.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; +using MudBlazor.Utilities; namespace MudBlazor; @@ -16,6 +17,18 @@ public partial class MudTabPanel { private Boolean _disposed; + protected string Stylename => + new StyleBuilder() + .AddStyle("display", Parent?.ActivePanel == this ? "contents" : "none", Parent?.KeepPanelsAlive == true) + .AddStyle(Style) + .Build(); + + internal string Classname => + new CssBuilder("mud-tab-panel") + .AddClass("mud-tab-panel-hidden", !Visible) + .AddClass(Class) + .Build(); + [CascadingParameter] private MudTabs? Parent { get; set; } @@ -64,6 +77,16 @@ public partial class MudTabPanel [Category(CategoryTypes.Tabs.Behavior)] public bool Disabled { get; set; } + /// + /// Shows the tab. + /// + /// + /// Defaults to true. + /// + [Parameter] + [Category(CategoryTypes.FormComponent.Behavior)] + public bool Visible { get; set; } = true; + /// /// For dynamic tabs, shows a "Close" icon for this tab. /// diff --git a/src/MudBlazor/Components/Tabs/MudTabs.razor.cs b/src/MudBlazor/Components/Tabs/MudTabs.razor.cs index b8cf22aaf4a2..942d398014d5 100644 --- a/src/MudBlazor/Components/Tabs/MudTabs.razor.cs +++ b/src/MudBlazor/Components/Tabs/MudTabs.razor.cs @@ -571,7 +571,7 @@ public void ActivatePanel(object id, bool ignoreDisabledState = false) private async void ActivatePanel(MudTabPanel panel, MouseEventArgs? ev, bool ignoreDisabledState = false) { - if (!panel.Disabled || ignoreDisabledState) + if ((panel.Visible && !panel.Disabled) || ignoreDisabledState) { var index = _panels.IndexOf(panel); var previewArgs = new TabInteractionEventArgs @@ -700,7 +700,7 @@ string GetTabClass(MudTabPanel panel) .AddClass($"mud-ripple", Ripple) .AddClass(ActiveTabClass, when: () => panel == ActivePanel) .AddClass(TabPanelClass) - .AddClass(panel.Class) + .AddClass(panel.Classname) .Build(); return tabClass; diff --git a/src/MudBlazor/Services/Browser/BrowserViewportService.cs b/src/MudBlazor/Services/Browser/BrowserViewportService.cs index 05044040c81f..cbb4c1510d90 100644 --- a/src/MudBlazor/Services/Browser/BrowserViewportService.cs +++ b/src/MudBlazor/Services/Browser/BrowserViewportService.cs @@ -101,7 +101,7 @@ public async Task SubscribeAsync(IBrowserViewportObserver observer, bool fireImm optionsClone.BreakpointDefinitions = BreakpointGlobalOptions.GetDefaultOrUserDefinedBreakpointDefinition(optionsClone, ResizeOptions); var subscription = await CreateJavaScriptListener(optionsClone, observer.Id); - if (_observerManager.Observers.ContainsKey(subscription)) + if (_observerManager.IsSubscribed(subscription)) { // Only re-subscribe _observerManager.Subscribe(subscription, observer); @@ -271,9 +271,8 @@ public async ValueTask DisposeAsync() internal BrowserViewportSubscription? GetInternalSubscription(Guid observerId) { var subscription = _observerManager - .Observers - .Select(x => x.Key) - .FirstOrDefault(x => x.ObserverId == observerId); + .FindObserverIdentities((key, _) => key.ObserverId == observerId) + .FirstOrDefault(); return subscription; } @@ -284,9 +283,8 @@ private async Task CreateJavaScriptListener(ResizeO { // We check if we have an observer with equals options or same observer id var javaScriptListenerId = _observerManager - .Observers - .Where(x => clonedOptions.Equals(x.Key.Options ?? clonedOptions) || x.Key.ObserverId == observerId) - .Select(x => x.Key.JavaScriptListenerId) + .FindObserverIdentities((key, _) => clonedOptions.Equals(key.Options ?? clonedOptions) || key.ObserverId == observerId) + .Select(x => x.JavaScriptListenerId) .FirstOrDefault(); // This implementation serves as an optimization to avoid creating a new JavaScript "listener" each time a subscription occurs. @@ -316,7 +314,7 @@ private async Task CreateJavaScriptListener(ResizeO return null; } - var observersWithSameJsListenerIdCount = _observerManager.Observers.Keys.Count(x => x.JavaScriptListenerId == subscription.JavaScriptListenerId); + var observersWithSameJsListenerIdCount = _observerManager.FindObserverIdentities((key, _) => key.JavaScriptListenerId == subscription.JavaScriptListenerId).Count(); if (observersWithSameJsListenerIdCount == 1) { diff --git a/src/MudBlazor/Services/KeyInterceptor/KeyInterceptorService.cs b/src/MudBlazor/Services/KeyInterceptor/KeyInterceptorService.cs index 93e5ae001f68..0308e12b124e 100644 --- a/src/MudBlazor/Services/KeyInterceptor/KeyInterceptorService.cs +++ b/src/MudBlazor/Services/KeyInterceptor/KeyInterceptorService.cs @@ -54,7 +54,7 @@ public async Task SubscribeAsync(IKeyInterceptorObserver observer, KeyIntercepto return; } - if (!_observerManager.Observers.ContainsKey(observer.ElementId)) + if (!_observerManager.IsSubscribed(observer.ElementId)) { var isConnected = await _keyInterceptorInterop.Connect(_dotNetReferenceLazy.Value, observer.ElementId, options); if (isConnected) @@ -158,9 +158,9 @@ public async ValueTask DisposeAsync() _dotNetReferenceLazy.Value.Dispose(); } - foreach (var elementId in _observerManager.Observers.Keys) + foreach (var observer in _observerManager) { - await _keyInterceptorInterop.Disconnect(elementId); + await _keyInterceptorInterop.Disconnect(observer.ElementId); } _observerManager.Clear(); diff --git a/src/MudBlazor/Styles/components/_badge.scss b/src/MudBlazor/Styles/components/_badge.scss index 97776d1f14d1..bce4430f6dda 100644 --- a/src/MudBlazor/Styles/components/_badge.scss +++ b/src/MudBlazor/Styles/components/_badge.scss @@ -156,9 +156,6 @@ } } - &.center { - } - &.right { left: calc(100% - 4px); diff --git a/src/MudBlazor/Styles/components/_button.scss b/src/MudBlazor/Styles/components/_button.scss index 3f0d81ec0849..82123377fa01 100644 --- a/src/MudBlazor/Styles/components/_button.scss +++ b/src/MudBlazor/Styles/components/_button.scss @@ -57,13 +57,6 @@ } } -.mud-button-label { - width: 100%; - display: inherit; - align-items: inherit; - justify-content: inherit; -} - .mud-button-text { padding: 6px 8px; @@ -271,6 +264,11 @@ } .mud-button-label { + width: 100%; + display: inherit; + align-items: inherit; + justify-content: inherit; + .mud-button-icon-start { display: inherit; margin-left: -4px; diff --git a/src/MudBlazor/Styles/components/_buttongroup.scss b/src/MudBlazor/Styles/components/_buttongroup.scss index cd3048b4d55e..63ff7e63aeec 100644 --- a/src/MudBlazor/Styles/components/_buttongroup.scss +++ b/src/MudBlazor/Styles/components/_buttongroup.scss @@ -30,6 +30,97 @@ } } } + + &.mud-button-group-text-size-small .mud-button-root { + padding: 4px 5px; + font-size: 0.8125rem; + + &.mud-icon-button .mud-icon-root { + font-size: 1.422rem; + } + } + + &.mud-button-group-text-size-large .mud-button-root { + padding: 8px 11px; + font-size: 0.9375rem; + + &.mud-icon-button .mud-icon-root { + font-size: 1.641rem; + } + } + + &.mud-button-group-outlined-size-small .mud-button-root { + padding: 3px 9px; + font-size: 0.8125rem; + + &.mud-icon-button { + padding: 3px 9px; + + .mud-icon-root { + font-size: 1.422rem; + } + } + } + + &.mud-button-group-outlined-size-large .mud-button-root { + padding: 7px 21px; + font-size: 0.9375rem; + + &.mud-icon-button { + padding: 7px 15px; + + .mud-icon-root { + font-size: 1.641rem; + } + } + } + + &.mud-button-group-filled-size-small .mud-button-root { + padding: 4px 10px; + font-size: 0.8125rem; + + &.mud-icon-button { + padding: 4px 10px; + + .mud-icon-root { + font-size: 1.422rem; + } + } + } + + &.mud-button-group-filled-size-large .mud-button-root { + padding: 8px 22px; + font-size: 0.9375rem; + + &.mud-icon-button { + padding: 8px 16px; + + .mud-icon-root { + font-size: 1.641rem; + } + } + } + + .mud-button-root.mud-icon-button { + padding-right: 12px; + padding-left: 12px; + + .mud-icon-root { + font-size: 1.516rem; + } + + &.mud-ripple-icon { + &:after { + transform: scale(10,10); + } + + &:active:after { + transform: scale(0,0); + opacity: 0.1; + transition: 0s; + } + } + } } .mud-button-group-horizontal { @@ -262,98 +353,3 @@ .mud-button-group-disable-elevation { box-shadow: none; } - -.mud-button-group-root { - &.mud-button-group-text-size-small .mud-button-root { - padding: 4px 5px; - font-size: 0.8125rem; - - &.mud-icon-button .mud-icon-root { - font-size: 1.422rem; - } - } - - &.mud-button-group-text-size-large .mud-button-root { - padding: 8px 11px; - font-size: 0.9375rem; - - &.mud-icon-button .mud-icon-root { - font-size: 1.641rem; - } - } - - &.mud-button-group-outlined-size-small .mud-button-root { - padding: 3px 9px; - font-size: 0.8125rem; - - &.mud-icon-button { - padding: 3px 9px; - - .mud-icon-root { - font-size: 1.422rem; - } - } - } - - &.mud-button-group-outlined-size-large .mud-button-root { - padding: 7px 21px; - font-size: 0.9375rem; - - &.mud-icon-button { - padding: 7px 15px; - - .mud-icon-root { - font-size: 1.641rem; - } - } - } - - &.mud-button-group-filled-size-small .mud-button-root { - padding: 4px 10px; - font-size: 0.8125rem; - - &.mud-icon-button { - padding: 4px 10px; - - .mud-icon-root { - font-size: 1.422rem; - } - } - } - - &.mud-button-group-filled-size-large .mud-button-root { - padding: 8px 22px; - font-size: 0.9375rem; - - &.mud-icon-button { - padding: 8px 16px; - - .mud-icon-root { - font-size: 1.641rem; - } - } - } -} - -.mud-button-group-root { - .mud-button-root.mud-icon-button { - padding-right: 12px; - padding-left: 12px; - - .mud-icon-root { - font-size: 1.516rem; - } - - &.mud-ripple-icon { - &:after { - transform: scale(10,10); - } - - &:active:after { - transform: scale(0,0); - opacity: 0.1; - transition: 0s; - } - } - } -} diff --git a/src/MudBlazor/Styles/components/_datagrid.scss b/src/MudBlazor/Styles/components/_datagrid.scss index 731c7a3b1fca..f5f39ad385e4 100644 --- a/src/MudBlazor/Styles/components/_datagrid.scss +++ b/src/MudBlazor/Styles/components/_datagrid.scss @@ -21,8 +21,6 @@ .mud-table-cell { &.edit-mode-cell { - //padding: 0 !important; - .mud-input-control { margin: 0 !important; } @@ -104,7 +102,6 @@ .sort-direction-icon { font-size: 18px; margin-left: 4px; - margin-left: 4px; margin-inline-start: 4px; margin-inline-end: unset; user-select: none; @@ -127,9 +124,7 @@ } .column-options .mud-menu .mud-icon-button-label { - /*font-size: 18px;*/ user-select: none; - /*margin-right: 4px;*/ transition: opacity 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,transform 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; opacity: 0; } @@ -179,7 +174,6 @@ .sort-direction-icon { font-size: 18px; margin-left: 4px; - margin-left: 4px; margin-inline-start: 4px; margin-inline-end: unset; user-select: none; diff --git a/src/MudBlazor/Styles/components/_input.scss b/src/MudBlazor/Styles/components/_input.scss index 749f9807990b..df260c064e05 100644 --- a/src/MudBlazor/Styles/components/_input.scss +++ b/src/MudBlazor/Styles/components/_input.scss @@ -149,6 +149,7 @@ box-sizing: border-box; width: 100%; max-width: 100%; + min-width: 0%; height: 100%; text-align: start; pointer-events: none; @@ -359,7 +360,6 @@ box-sizing: content-box; letter-spacing: inherit; -webkit-tap-highlight-color: transparent; - height: auto; resize: none; cursor: auto; @@ -501,16 +501,14 @@ white-space: nowrap; } -.mud-input-adornment-start { - &.mud-input-root-filled-shrink { - margin-top: 16px; - } -} - .mud-input-adornment-start { margin-right: 8px; margin-inline-end: 8px; margin-inline-start: unset; + + &.mud-input-root-filled-shrink { + margin-top: 16px; + } } .mud-input-adornment-end { diff --git a/src/MudBlazor/Styles/components/_inputcontrol.scss b/src/MudBlazor/Styles/components/_inputcontrol.scss index 0159a7fe79f9..27a412e2aa8f 100644 --- a/src/MudBlazor/Styles/components/_inputcontrol.scss +++ b/src/MudBlazor/Styles/components/_inputcontrol.scss @@ -59,16 +59,6 @@ margin-top: 16px; } } - - &.mud-input.mud-input-filled { - &.mud-input-filled-with-label { - } - } - - &.mud-input.mud-input-outlined { - &.mud-input-outlined-with-label { - } - } } } @@ -83,7 +73,7 @@ padding: 0; font-size: 1rem; font-weight: 400; - line-height: 1; + line-height: 1.15rem; letter-spacing: 0.00938em; z-index: 0; pointer-events: none; @@ -158,14 +148,18 @@ bottom: 0; & button { - padding: 2px; + padding: 2px 0; min-width: unset; min-height: unset; } } + } - .mud-input-numeric-spin button { - padding: 2px 0; + &:focus-within, &.mud-input-error { + .mud-input-helper-text { + &.mud-input-helper-onfocus { + transform: translateY(0); + } } } } @@ -190,16 +184,6 @@ } } -.mud-input-control { - &:focus-within, &.mud-input-error { - .mud-input-helper-text { - &.mud-input-helper-onfocus { - transform: translateY(0); - } - } - } -} - .mud-input-helper-text.mud-disabled { color: var(--mud-palette-text-disabled); } diff --git a/src/MudBlazor/Styles/components/_inputlabel.scss b/src/MudBlazor/Styles/components/_inputlabel.scss index c5d24f961c86..fb3e250f4ede 100644 --- a/src/MudBlazor/Styles/components/_inputlabel.scss +++ b/src/MudBlazor/Styles/components/_inputlabel.scss @@ -24,7 +24,7 @@ } .mud-input-label-animated { - transition: color 200ms cubic-bezier(0.0, 0, 0.2, 1) 0ms,transform 200ms cubic-bezier(0.0, 0, 0.2, 1) 0ms; + transition: color 200ms cubic-bezier(0.0, 0, 0.2, 1) 0ms, transform 200ms cubic-bezier(0.0, 0, 0.2, 1) 0ms, max-width 200ms cubic-bezier(0.0, 0, 0.2, 1) 0ms; } .mud-input-label-filled { @@ -68,7 +68,7 @@ label.mud-input-label.mud-input-label-inputcontrol { &.mud-input-label-filled { transform: translate(12px, 10px) scale(0.75); - max-width: calc(100% - 12px); + max-width: calc((100% - 12px) / 0.75); &.mud-input-label-margin-dense { transform: translate(12px, 7px) scale(0.75); @@ -77,7 +77,7 @@ label.mud-input-label.mud-input-label-inputcontrol { &.mud-input-label-outlined { transform: translate(14px, -6px) scale(0.75); - max-width: calc(100% - 14px); + max-width: calc((100% - 14px) / 0.75); } } diff --git a/src/MudBlazor/Styles/components/_markdowncode.scss b/src/MudBlazor/Styles/components/_markdowncode.scss deleted file mode 100644 index d3a3c571ab95..000000000000 --- a/src/MudBlazor/Styles/components/_markdowncode.scss +++ /dev/null @@ -1,35 +0,0 @@ -.mud-markdown { - margin: 0px; - padding: 15px; - border-radius: var(--mud-default-borderradius); - background-color: var(--mud-palette-dark); - margin-bottom: 1.5rem; - - & .mud-markdown-pre { - white-space: normal; - } - - & .mud-markdown-code { - color: #fff; - font-size: 1em; - font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; - } -} - -.mud-markdown .mud-markdown-pre .mud-markdown-code { - - & mark { - color: #808078; - background-color: none; - } - - & mark { - color: #008080; - font-weight: bold; - background-color: none; - } -} - -.token.attr-value > .punctuation { - color: #d0d0d0; -} diff --git a/src/MudBlazor/Styles/components/_menu.scss b/src/MudBlazor/Styles/components/_menu.scss index 85c2b51848ab..deb401c043e8 100644 --- a/src/MudBlazor/Styles/components/_menu.scss +++ b/src/MudBlazor/Styles/components/_menu.scss @@ -64,9 +64,6 @@ margin: 4px 0; } - .mud-menu-submenu-icon { - } - &.mud-menu-item-dense { padding: 2px 12px; } diff --git a/src/MudBlazor/Styles/components/_navmenu.scss b/src/MudBlazor/Styles/components/_navmenu.scss index b92fefcee592..73a45d1a54bd 100644 --- a/src/MudBlazor/Styles/components/_navmenu.scss +++ b/src/MudBlazor/Styles/components/_navmenu.scss @@ -5,6 +5,88 @@ position: relative; list-style: none; overscroll-behavior-y: contain; + + &.mud-navmenu-dense { + .mud-nav-link { + padding: 4px 16px 4px 16px; + } + } + + &.mud-navmenu-margin-dense { + .mud-nav-link { + margin: 2px 0; + } + } + + &.mud-navmenu-margin-normal { + .mud-nav-link { + margin: 4px 0; + } + } + + &.mud-navmenu-rounded { + .mud-nav-link { + border-radius: var(--mud-default-borderradius); + } + } + + &.mud-navmenu-bordered { + .mud-nav-link { + &.active:not(.mud-nav-link-disabled) { + border-inline-end-style: solid; + border-inline-end-width: 2px; + } + } + } + + &.mud-navmenu-default { + .mud-nav-link.active:not(.mud-nav-link-disabled) { + color: var(--mud-palette-primary); + background-color: var(--mud-palette-action-default-hover); + + @media(hover: hover) and (pointer: fine) { + &:hover:not(.mud-nav-link-disabled) { + background-color: var(--mud-palette-action-default-hover); + } + } + + &:focus-visible:not(.mud-nav-link-disabled), &:active:not(.mud-nav-link-disabled) { + background-color: var(--mud-palette-action-default-hover); + } + } + + .mud-nav-link-expand-icon.mud-transform { + fill: var(--mud-palette-primary); + } + } + + @each $color in $mud-palette-colors { + &.mud-navmenu-#{$color} { + .mud-nav-link.active:not(.mud-nav-link-disabled) { + color: var(--mud-palette-#{$color}); + --mud-ripple-color: var(--mud-palette-#{$color}); + background-color: var(--mud-palette-#{$color}-hover); + + @media(hover: hover) and (pointer: fine) { + &:hover:not(.mud-nav-link-disabled) { + background-color: rgba(var(--mud-palette-#{$color}-rgb), 0.12); + } + } + + &:focus-visible:not(.mud-nav-link-disabled), &:active:not(.mud-nav-link-disabled) { + background-color: rgba(var(--mud-palette-#{$color}-rgb), 0.12); + } + + .mud-nav-link-icon { + color: var(--mud-palette-#{$color}); + } + } + + .mud-nav-link-expand-icon.mud-transform { + fill: var(--mud-palette-#{$color}); + } + } + } } .mud-nav-group { @@ -20,9 +102,7 @@ & > .mud-nav-link > .mud-nav-link-text { font-weight: 400; } - } - & * .mud-nav-group { & > .mud-nav-link.mud-expanded > .mud-nav-link-text { font-weight: 500; } @@ -57,7 +137,6 @@ color: inherit; line-height: 1.75; display: inline-flex; - align-items: center; justify-content: flex-start; text-transform: inherit; background-color: transparent; @@ -117,91 +196,6 @@ } } -.mud-navmenu { - - &.mud-navmenu-dense { - .mud-nav-link { - padding: 4px 16px 4px 16px; - } - } - - &.mud-navmenu-margin-dense { - .mud-nav-link { - margin: 2px 0; - } - } - - &.mud-navmenu-margin-normal { - .mud-nav-link { - margin: 4px 0; - } - } - - &.mud-navmenu-rounded { - .mud-nav-link { - border-radius: var(--mud-default-borderradius); - } - } - - &.mud-navmenu-bordered { - .mud-nav-link { - &.active:not(.mud-nav-link-disabled) { - border-inline-end-style: solid; - border-inline-end-width: 2px; - } - } - } - - &.mud-navmenu-default { - .mud-nav-link.active:not(.mud-nav-link-disabled) { - color: var(--mud-palette-primary); - background-color: var(--mud-palette-action-default-hover); - - @media(hover: hover) and (pointer: fine) { - &:hover:not(.mud-nav-link-disabled) { - background-color: var(--mud-palette-action-default-hover); - } - } - - &:focus-visible:not(.mud-nav-link-disabled), &:active:not(.mud-nav-link-disabled) { - background-color: var(--mud-palette-action-default-hover); - } - } - - .mud-nav-link-expand-icon.mud-transform { - fill: var(--mud-palette-primary); - } - } - - @each $color in $mud-palette-colors { - &.mud-navmenu-#{$color} { - .mud-nav-link.active:not(.mud-nav-link-disabled) { - color: var(--mud-palette-#{$color}); - --mud-ripple-color: var(--mud-palette-#{$color}); - background-color: var(--mud-palette-#{$color}-hover); - - @media(hover: hover) and (pointer: fine) { - &:hover:not(.mud-nav-link-disabled) { - background-color: rgba(var(--mud-palette-#{$color}-rgb), 0.12); - } - } - - &:focus-visible:not(.mud-nav-link-disabled), &:active:not(.mud-nav-link-disabled) { - background-color: rgba(var(--mud-palette-#{$color}-rgb), 0.12); - } - - .mud-nav-link-icon { - color: var(--mud-palette-#{$color}); - } - } - - .mud-nav-link-expand-icon.mud-transform { - fill: var(--mud-palette-#{$color}); - } - } - } -} - .mud-nav-group * .mud-navmenu > .mud-nav-group { & .mud-nav-link { padding-left: 36px; diff --git a/src/MudBlazor/Styles/components/_picker.scss b/src/MudBlazor/Styles/components/_picker.scss index 2e7d514f4ed9..a08aa6f257e5 100644 --- a/src/MudBlazor/Styles/components/_picker.scss +++ b/src/MudBlazor/Styles/components/_picker.scss @@ -95,7 +95,6 @@ .mud-picker-content { display: flex; - //max-width: 325px; max-width: 100%; min-width: 310px; min-height: 305px; diff --git a/src/MudBlazor/Styles/components/_pickercolor.scss b/src/MudBlazor/Styles/components/_pickercolor.scss index b31decfe4e96..a9cdca2b6fd0 100644 --- a/src/MudBlazor/Styles/components/_pickercolor.scss +++ b/src/MudBlazor/Styles/components/_pickercolor.scss @@ -51,6 +51,19 @@ /*Let the overlay handle the clicks*/ pointer-events: none; } + + .mud-picker-color-grid { + display: flex; + flex-wrap: wrap; + + .mud-picker-color-dot { + height: 25px; + min-width: 26px; + max-width: 26px; + border-radius: 0px; + box-shadow: none; + } + } } .mud-picker-color-controls { @@ -232,18 +245,3 @@ } } } - -.mud-picker-color-picker { - .mud-picker-color-grid { - display: flex; - flex-wrap: wrap; - - .mud-picker-color-dot { - height: 25px; - min-width: 26px; - max-width: 26px; - border-radius: 0px; - box-shadow: none; - } - } -} diff --git a/src/MudBlazor/Styles/components/_pickerdate.scss b/src/MudBlazor/Styles/components/_pickerdate.scss index c17063f8709a..12b4e2035885 100644 --- a/src/MudBlazor/Styles/components/_pickerdate.scss +++ b/src/MudBlazor/Styles/components/_pickerdate.scss @@ -122,7 +122,6 @@ height: 60px; display: flex; outline: none; - transition: font-size 100ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; align-items: center; justify-content: center; transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; diff --git a/src/MudBlazor/Styles/components/_popover.scss b/src/MudBlazor/Styles/components/_popover.scss index e69c79930550..8ddd8aa6b43e 100644 --- a/src/MudBlazor/Styles/components/_popover.scss +++ b/src/MudBlazor/Styles/components/_popover.scss @@ -22,6 +22,11 @@ transition-duration: 0ms !important; transition-delay: 0ms !important; } + + .mud-list { + max-height: inherit; + overflow-y: auto; + } } .mud-popover .mud-popover { @@ -58,12 +63,4 @@ .mud-popover-cascading-value { z-index: calc(var(--mud-zindex-select) + 5); } - -} - -.mud-popover { - .mud-list { - max-height: inherit; - overflow-y: auto; - } } diff --git a/src/MudBlazor/Styles/components/_simpletable.scss b/src/MudBlazor/Styles/components/_simpletable.scss index 0254203c06ab..185ce30de7f6 100644 --- a/src/MudBlazor/Styles/components/_simpletable.scss +++ b/src/MudBlazor/Styles/components/_simpletable.scss @@ -13,6 +13,12 @@ tbody { display: table-row-group; + + tr:last-child { + td { + border-bottom: none; + } + } } * tr { @@ -38,14 +44,6 @@ line-height: 1.5rem; } } - - tbody { - tr:last-child { - td { - border-bottom: none; - } - } - } } } diff --git a/src/MudBlazor/Styles/components/_table.scss b/src/MudBlazor/Styles/components/_table.scss index c6bf64e3f9d0..1ba5f58d0704 100644 --- a/src/MudBlazor/Styles/components/_table.scss +++ b/src/MudBlazor/Styles/components/_table.scss @@ -33,7 +33,15 @@ line-height: 1.5rem; & .mud-button-root { - user-select: auto; + @media(hover: hover) and (pointer: fine) { + &:hover { + color: var(--mud-palette-action-default); + + .mud-table-sort-label-icon { + opacity: 0.8; + } + } + } } } } @@ -57,22 +65,12 @@ } .mud-table-sort-label { - cursor: pointer; + user-select: auto; display: inline-flex; align-items: center; flex-direction: inherit; justify-content: flex-start; - @media(hover: hover) and (pointer: fine) { - &:hover { - color: var(--mud-palette-action-default); - - .mud-table-sort-label-icon { - opacity: 0.8; - } - } - } - .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; @@ -122,6 +120,26 @@ padding: 4px; } } + + & > .mud-input-control { + & > div.mud-input.mud-input-text { + color: var(--mud-theme-on-surface); + font-size: 0.875rem; + margin-top: -14px; + margin-bottom: -8px; + } + } + + & > .mud-select { + & > .mud-input-control { + & > div.mud-input.mud-input-text { + color: var(--mud-theme-on-surface); + font-size: 0.875rem; + margin-top: -14px; + margin-bottom: -8px; + } + } + } } .mud-table-cell-footer { @@ -185,34 +203,25 @@ & .mud-table-bordered { & .mud-table-container .mud-table-root .mud-table-body { & .mud-table-row { - .mud-table-cell:last-child { - border-right: 1px solid var(--mud-palette-table-lines); + .mud-table-cell:first-child { + border-right: none; border-top-right-radius: 0px; } - } - } - & .mud-table-container .mud-table-root .mud-table-body { - & .mud-table-row { - .mud-table-cell:first-child { - border-right: none; + .mud-table-cell:last-child { + border-right: 1px solid var(--mud-palette-table-lines); border-top-right-radius: 0px; } } } - & .mud-table-container .mud-table-root .mud-table-head.table-head-bordered { & .mud-table-row { .mud-table-cell:last-child { border-right: 1px solid var(--mud-palette-table-lines); border-top-right-radius: 0px; } - } - } - & .mud-table-container .mud-table-root .mud-table-head.table-head-bordered { - & .mud-table-row { .mud-table-cell:only-child { border-right: none; border-top-right-radius: var(--mud-default-borderradius); @@ -220,7 +229,6 @@ } } - & .mud-table-container .mud-table-root .mud-table-head { & .mud-table-row { th.mud-table-cell:first-child { @@ -272,8 +280,19 @@ top: 0; } + & * .mud-table-loading { + background-color: var(--mud-palette-surface); + position: sticky; + z-index: 2; + top: 59px; + } + & * .mud-filter-panel-cell { - top: 58px; + top: 59px; + } + // If .mud-table-loading exists, move .mud-filter-panel-cell down + table:has(.mud-table-loading) & * .mud-filter-panel-cell { + top: 63px; } & * .mud-table-cell.sticky-left, @@ -288,9 +307,18 @@ .mud-table-sticky-header.mud-table-dense { & * .mud-table-root { .mud-table-head { + + & * .mud-table-loading { + top: 39px; + } + & * .mud-filter-panel-cell { top: 39px; } + + table:has(.mud-table-loading) & * .mud-filter-panel-cell { + top: 43px; + } } } } @@ -350,29 +378,6 @@ } } - -.mud-table-cell { - & > .mud-input-control { - & > div.mud-input.mud-input-text { - color: var(--mud-theme-on-surface); - font-size: 0.875rem; - margin-top: -14px; - margin-bottom: -8px; - } - } - - & > .mud-select { - & > .mud-input-control { - & > div.mud-input.mud-input-text { - color: var(--mud-theme-on-surface); - font-size: 0.875rem; - margin-top: -14px; - margin-bottom: -8px; - } - } - } -} - .mud-table-cell-align { &-left { text-align: left; @@ -611,9 +616,7 @@ } } } - } - &.mud-table-bordered { & .mud-table-container .mud-table-root .mud-table-body { & .mud-table-row { .mud-table-cell { diff --git a/src/MudBlazor/Styles/components/_tabs.scss b/src/MudBlazor/Styles/components/_tabs.scss index 02ea7a857ed7..6534fb04db18 100644 --- a/src/MudBlazor/Styles/components/_tabs.scss +++ b/src/MudBlazor/Styles/components/_tabs.scss @@ -172,6 +172,10 @@ margin-inline-end: 8px; margin-inline-start: unset; } + + &.mud-tab-panel-hidden { + display: none; + } } .mud-tab-slider { diff --git a/src/MudBlazor/Styles/components/_timeline.scss b/src/MudBlazor/Styles/components/_timeline.scss index 170a363ecf0d..50ae6d0ba609 100644 --- a/src/MudBlazor/Styles/components/_timeline.scss +++ b/src/MudBlazor/Styles/components/_timeline.scss @@ -4,19 +4,30 @@ display: flex; } -.mud-timeline-vertical { - padding-top: 24px; - flex-direction: column; +.mud-timeline-item { + display: flex; - &::before { - top: 0; - bottom: 0; - content: ""; + .mud-timeline-item-content { + position: relative; height: 100%; - position: absolute; - width: 2px; - background: var(--mud-palette-divider); + flex: 1 1 auto; + } + + .mud-timeline-item-divider { + position: relative; + display: flex; + align-items: center; + justify-content: center; + } + + .mud-timeline-item-opposite { + align-self: center; } +} + +.mud-timeline-vertical { + padding-top: 24px; + flex-direction: column; .mud-timeline-item { padding-bottom: 24px; @@ -34,59 +45,17 @@ max-width: calc(50% - 48px); } } -} - -.mud-timeline-horizontal { - flex-direction: row; &::before { top: 0; bottom: 0; content: ""; - height: 2px; + height: 100%; position: absolute; - width: 100%; + width: 2px; background: var(--mud-palette-divider); } - .mud-timeline-item { - padding: 0 24px; - width: 100%; - min-width: 0; - - .mud-timeline-item-content { - max-height: calc(50% - 48px); - } - - .mud-timeline-item-divider { - min-height: 96px; - } - } -} - -.mud-timeline-item { - display: flex; - - .mud-timeline-item-content { - position: relative; - height: 100%; - flex: 1 1 auto; - } - - .mud-timeline-item-divider { - position: relative; - display: flex; - align-items: center; - justify-content: center; - } - - .mud-timeline-item-opposite { - align-self: center; - } -} - - -.mud-timeline-vertical { &.mud-timeline-align-start { .mud-timeline-item-divider { align-items: flex-start; @@ -202,6 +171,32 @@ } .mud-timeline-horizontal { + flex-direction: row; + + .mud-timeline-item { + padding: 0 24px; + width: 100%; + min-width: 0; + + .mud-timeline-item-content { + max-height: calc(50% - 48px); + } + + .mud-timeline-item-divider { + min-height: 96px; + } + } + + &::before { + top: 0; + bottom: 0; + content: ""; + height: 2px; + position: absolute; + width: 100%; + background: var(--mud-palette-divider); + } + &.mud-timeline-align-start { .mud-timeline-item-divider { justify-content: flex-start; @@ -372,9 +367,29 @@ .mud-timeline-modifiers { .mud-timeline-item-content { .mud-card { - &::before, &::after { + &::before { content: ""; position: absolute; + border-top: 16px solid transparent; + border-bottom: 16px solid transparent; + border-right: 16px solid rgba(0,0,0,.10); + top: calc(50% - 14px); + } + + &::after { + content: ""; + position: absolute; + border-top: 16px solid transparent; + border-bottom: 16px solid transparent; + border-right: 16px solid var(--mud-palette-surface); + top: calc(50% - 16px); + } + + &.mud-paper-outlined { + &::before { + top: calc(50% - 16px); + border-right-color: var(--mud-palette-lines-default); + } } } } @@ -620,31 +635,4 @@ } } } - - - - .mud-timeline-item-content { - .mud-card { - &::before { - border-top: 16px solid transparent; - border-bottom: 16px solid transparent; - border-right: 16px solid rgba(0,0,0,.10); - top: calc(50% - 14px); - } - - &::after { - border-top: 16px solid transparent; - border-bottom: 16px solid transparent; - border-right: 16px solid var(--mud-palette-surface); - top: calc(50% - 16px); - } - - &.mud-paper-outlined { - &::before { - top: calc(50% - 16px); - border-right-color: var(--mud-palette-lines-default); - } - } - } - } } diff --git a/src/MudBlazor/Styles/layout/_drawer.scss b/src/MudBlazor/Styles/layout/_drawer.scss index 1a834b2b5a4b..cb47860c658e 100644 --- a/src/MudBlazor/Styles/layout/_drawer.scss +++ b/src/MudBlazor/Styles/layout/_drawer.scss @@ -2,8 +2,6 @@ .mud-drawer { display: flex; - flex-shrink: 0; - flex-grow: 1; flex: 0 0 auto; outline: 0; position: fixed; @@ -18,8 +16,6 @@ height: 100%; max-height: 100%; display: flex; - flex-shrink: 0; - flex-grow: 1; flex: 0 0 auto; flex-direction: column; } diff --git a/src/MudBlazor/Styles/utilities/flexbox/_flex-direction.scss b/src/MudBlazor/Styles/utilities/flexbox/_flex-direction.scss index c106f76b9126..ad63c087ce8f 100644 --- a/src/MudBlazor/Styles/utilities/flexbox/_flex-direction.scss +++ b/src/MudBlazor/Styles/utilities/flexbox/_flex-direction.scss @@ -18,4 +18,4 @@ } } -@include flex-direction(""); +@include flex-direction(""); \ No newline at end of file diff --git a/src/MudBlazor/Utilities/ObserverManager/ObserverManager.cs b/src/MudBlazor/Utilities/ObserverManager/ObserverManager.cs index 6863c6a7753b..ed76188cd1b2 100644 --- a/src/MudBlazor/Utilities/ObserverManager/ObserverManager.cs +++ b/src/MudBlazor/Utilities/ObserverManager/ObserverManager.cs @@ -4,6 +4,7 @@ using System.Collections; using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Logging; namespace MudBlazor.Utilities.ObserverManager; @@ -50,6 +51,38 @@ public ObserverManager(ILogger log) /// public void Clear() => _observers.Clear(); + /// + /// Checks if an observer with the specified identity is subscribed. + /// + /// The identity of the observer. + /// True if the observer is subscribed; otherwise, false. + public bool IsSubscribed(TIdentity id) => _observers.ContainsKey(id); + + /// + /// Tries to get the subscription for the specified identity. + /// + /// The identity of the observer. + /// When this method returns, contains the observer associated with the specified identity, if the identity is found; otherwise, the default value for the type of the observer parameter. + /// True if the observer is found; otherwise, false. + public bool TryGetSubscription(TIdentity id, [MaybeNullWhen(false)] out TObserver observer) + { + if (_observers.TryGetValue(id, out var entry)) + { + observer = entry.Observer; + return true; + } + observer = default; + return false; + } + + /// + /// Finds the identities of observers that match the specified predicate. + /// + /// The predicate to filter the observers. + /// An enumerable collection of observer identities that match the predicate. + public IEnumerable FindObserverIdentities(Func predicate) => + _observers.Where(kvp => predicate(kvp.Key, kvp.Value.Observer)).Select(kvp => kvp.Key); + /// /// Ensures that the provided is subscribed, renewing its subscription. /// diff --git a/tools/CheckPackageContainsStaticAssets.ps1 b/tools/CheckPackageContainsStaticAssets.ps1 index f9e38041eea1..23b2cb385d82 100644 --- a/tools/CheckPackageContainsStaticAssets.ps1 +++ b/tools/CheckPackageContainsStaticAssets.ps1 @@ -9,6 +9,6 @@ if($fileNames.Contains($assetName)) } else { - Write-Error -Message "Static asset check - $nupkgFilePath does not contain $assetNamh" + Write-Error -Message "Static asset check - $nupkgFilePath does not contain $assetName" exit 1 }