diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index efbba9aa9be6..94ea70820688 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -33,10 +33,6 @@ jobs: console.log("PR type: documentation."); } - if(!pr.draft) { - labels.push("PR: needs review"); - } - if(labels.length != 0) { github.rest.issues.addLabels({ issue_number: context.issue.number, diff --git a/src/MudBlazor.Docs/Pages/Components/Autocomplete/AutocompletePage.razor b/src/MudBlazor.Docs/Pages/Components/Autocomplete/AutocompletePage.razor index 973c65e53645..f11e9d96e993 100644 --- a/src/MudBlazor.Docs/Pages/Components/Autocomplete/AutocompletePage.razor +++ b/src/MudBlazor.Docs/Pages/Components/Autocomplete/AutocompletePage.razor @@ -139,13 +139,11 @@ - This component uses MudPopover - to place its list of items in combination with MudOverlay. - Some of these settings can be controlled with DropdownSettings, - which contain defaults for the MudPopover appearance and behavior. - - Read more on popover's page. - + This component leverages MudPopover along with + MudOverlay to manage its item list. + You can configure certain aspects using DropdownSettings, + which sets default appearance and behavior properties. + Learn more on the popover documentation page. diff --git a/src/MudBlazor.Docs/Pages/Components/DataGrid/DataGridPage.razor b/src/MudBlazor.Docs/Pages/Components/DataGrid/DataGridPage.razor index acfe972321dd..e2e787c1d8bf 100644 --- a/src/MudBlazor.Docs/Pages/Components/DataGrid/DataGridPage.razor +++ b/src/MudBlazor.Docs/Pages/Components/DataGrid/DataGridPage.razor @@ -96,14 +96,33 @@ - The <MudDataGrid> allows you to group data by columns. Setting the Grouping property - to true enables grouping, adding a menu item in the column options to toggle grouping for that column. - To disable grouping for a specific column, set its Groupable property to false. - - You can group multiple Column elements at the same time. Options for configuring column grouping include the two-way bindable - properties Grouping, GroupExpanded, and GroupByOrder. - - The GroupBy property allows you to define a custom function for grouping a column. + The MudDataGrid allows users to organize data by grouping rows that share common values in + selected columns. Grouping collapses similar data into expandable sections, making large datasets more manageable. + Enabling Grouping + + To enable grouping functionality across the grid set the Groupable property on MudDataGrid + to true. This activates the grouping feature globally, allowing all columns to be groupable unless explicitly overridden. + Each column also supports its own Groupable property. This column-level property takes precedence over the grid-level + setting. For example, if the grid’s Groupable is true but a column’s + Groupable is set to false, that column will not be groupable via the UI. + + Default Grouping State + + To define whether a column should start grouped by default, set its Grouping property to true. + This property supports two-way binding @@bind-Grouping, meaning it updates if the user groups or ungroups the column + via the grid’s UI. + + + Note that for Grouping to take effect, either the column or grid must have Groupable enabled. + + Additional Features + + Multi-level grouping is supported, allowing hierarchical organization by multiple columns simultaneously + Group headers display the grouped column name or GroupTemplate, with expansion controls + + Grouping, GroupExpanded, and GroupByOrder are two-way bindable column parameters. + + diff --git a/src/MudBlazor.Docs/Pages/Components/DataGrid/Examples/DataGridGroupingExample.razor b/src/MudBlazor.Docs/Pages/Components/DataGrid/Examples/DataGridGroupingExample.razor index 90ebc3988392..00fcfa51e476 100644 --- a/src/MudBlazor.Docs/Pages/Components/DataGrid/Examples/DataGridGroupingExample.razor +++ b/src/MudBlazor.Docs/Pages/Components/DataGrid/Examples/DataGridGroupingExample.razor @@ -9,6 +9,15 @@ Periodic Elements + + + @context.Title: @context.Grouping.Key Count: @context.Grouping.Count() + @if (context.DataGrid.FilteredItems.Count() != 0) + { + @string.Format(" Percentage: {0:P1}", context.Grouping.Count() / ((double)context.DataGrid.FilteredItems.Count())) + } + + diff --git a/src/MudBlazor.Docs/Pages/Components/DataGrid/Examples/DataGridGroupingMultiLevelExample.razor b/src/MudBlazor.Docs/Pages/Components/DataGrid/Examples/DataGridGroupingMultiLevelExample.razor index bfb969022314..42303ef1b2c9 100644 --- a/src/MudBlazor.Docs/Pages/Components/DataGrid/Examples/DataGridGroupingMultiLevelExample.razor +++ b/src/MudBlazor.Docs/Pages/Components/DataGrid/Examples/DataGridGroupingMultiLevelExample.razor @@ -17,9 +17,18 @@ @bind-Value="_searchString" @bind-Value:after="@(() => _dataGrid.ReloadServerData())" DebounceInterval="100" Variant="Variant.Outlined" Clearable /> + + + @context.Title: @context.Grouping.Key Count: @context.Grouping.Count() + @if (context.DataGrid.FilteredItems.Count() != 0) + { + @string.Format(" Percentage: {0:P1}", context.Grouping.Count() / ((double)context.DataGrid.FilteredItems.Count())) + } + + - + - This component uses MudPopover - to place its list of items in combination with MudOverlay. - Some of these settings can be controlled with DropdownSettings, - which contain defaults for the MudPopover appearance and behavior. - - Read more on popover's page. - + This component leverages MudPopover along with + MudOverlay to manage its item list. + You can configure certain aspects using DropdownSettings, + which sets default appearance and behavior properties. + Learn more on the popover documentation page. + diff --git a/src/MudBlazor.Docs/Pages/Components/Overlay/OverlayPage.razor b/src/MudBlazor.Docs/Pages/Components/Overlay/OverlayPage.razor index de3323249874..c037a2a9edce 100644 --- a/src/MudBlazor.Docs/Pages/Components/Overlay/OverlayPage.razor +++ b/src/MudBlazor.Docs/Pages/Components/Overlay/OverlayPage.razor @@ -6,7 +6,9 @@ - The AutoClose property is used to let users close the overlay by clicking it. + + Use the AutoClose property to allow users to close the overlay by clicking anywhere on it. + @@ -15,7 +17,9 @@ - The overlay can be contained inside its parent using the Absolute property and CSS Style="position: relative;". + + The overlay can be contained inside its parent by setting the Absolute property along with the relative class (or CSS position: relative style). + @@ -24,7 +28,9 @@ - The overlay is transparent by default but can be changed with DarkBackground or LightBackground. + + The overlay's default color is transparent, but you can adjust its appearance using either the DarkBackground or LightBackground properties. + @@ -32,8 +38,10 @@ - - With the ZIndex property you can control the stack order of the component. + + + Control the stacking order of the overlay relative to other elements by adjusting the ZIndex property. + @@ -42,7 +50,9 @@ - The Overlay component can take any child content but here we are using it to display loading progress. + + The overlay can contain any child content but here it's used to display loading progress. + @@ -50,4 +60,4 @@ - \ No newline at end of file + diff --git a/src/MudBlazor.Docs/Pages/Components/Popover/PopoverPage.razor b/src/MudBlazor.Docs/Pages/Components/Popover/PopoverPage.razor index 4b7db393392c..fcbdd2b08cb9 100644 --- a/src/MudBlazor.Docs/Pages/Components/Popover/PopoverPage.razor +++ b/src/MudBlazor.Docs/Pages/Components/Popover/PopoverPage.razor @@ -2,52 +2,53 @@ - - - Note: When using this component it can be good to have some CSS knowledge it might not serve all types of content out of the box. - + - - - The popover's open state is completely up to you, as well as the content of it. - - - - - + + + + You have full control over the popover's content and its open state. + + + + + + - - - - Note: the location can be set with custom css or using the style tag. - Control where the popover should start from relative to the parent. Offset the popover to be located outside of the parent. - - - - - - + + + + Define the starting point and offset of the popover relative to its parent component. + You can set the popover location either through custom CSS or directly via inline styles. + + + + + + - - - - You can set the overflow behavior of the popover to either FlipNever, FilpOnOpen or FlipAlways
- Resize your browser window slowly so the popover wont fit to see result in flip mode. -
-
- + + + + Configure the popover's overflow behavior using one of the following options: + FlipNever, FlipOnOpen, or FlipAlways.
+ When space is limited, the popover can flip its position to stay visible. In center mode, the popover flips vertically. Scroll the page to see this behavior in action. +
+
+ - -
+
+
- - - - You can have any content within a popover like with any other Blazor component. The position of the popover is updated automatically - - - + + + + + Popovers support complex content and adjust their position automatically to fit their surroundings, just like any other Blazor component. + + + @@ -55,10 +56,12 @@ - Popovers can be placed relative to their Activator or Parent. The RelativeWidth is an enum DropdownWidth, - with a default value of DropdownWidth.Ignore which means the width is not associated to the Activator Width and can grow or shrink as needed. - Additional options are DropdownWidth.Relative which constrains the width of the popover to be the same size as the Activator Width and cannot grow. - DropdownWidth.Adaptive which constrains the minimum width of the popover to be the same size as the Activator / Parent but can grow wider as needed. + Popovers can have their widths set relative to their activator or parent through the DropdownWidth enum: +
    +
  • Ignore (default): Popover width adjusts independently of the activator.
  • +
  • Relative: Popover width matches the activator’s width precisely.
  • +
  • Adaptive: Minimum width matches activator or parent, but popover can grow wider as needed.
  • +
@@ -66,37 +69,37 @@
- - - - Popovers can be placed within elements that are using popover for itself. Like tooltips that are placed inside a menu. - - - - - - + + + + Popovers can be nested within elements that already use a popover, such as tooltips inside menus. + + + + + + - Some components use MudPopover to place their list of items in combination with MudOverlay. Some of these - settings can be controlled with DropdownSettings which contain defaults for the MudPopover - appearance and behavior. Default settings for DropdownSettings are: + Some components combine MudPopover with MudOverlay to manage item lists. These can be customized using DropdownSettings, which sets default appearance and behavior options:
    -
  • Fixed = false - Displays the dropdown popover in a fixed position, even while scrolling.
  • -
  • OverflowBehavior = OverflowBehavior.FlipOnOpen - The behavior applied when there is not enough space for the dropdown popover to be visible.
  • +
  • Fixed = false: Popover does not remain fixed during scrolling.
  • +
  • OverflowBehavior = OverflowBehavior.FlipOnOpen: Adjusts popover behavior when space is limited.
-
Some settings reside on the MudMenu component itself due to differences between different types of dropdowns.
These settings and their defaults are: +
Additional settings on the MudMenu component include:
    -
  • RelativeWidth = DropdownWidth.Ignore - The width of the dropdown popover is not dependent upon the Activator or Parent.
  • -
  • AnchorOrigin = Origin.BottomLeft - The location where the popover will open from.
  • +
  • RelativeWidth = DropdownWidth.Ignore: Width adjusts independently.
  • +
  • AnchorOrigin = Origin.BottomLeft: Popover opening position.
    • -
    • - * AnchorOrigin and TransformOrigin do not have to be set, and if not set will have a default Nested behavior that can be overridden. -
    • +
    • + + * Defaults apply if AnchorOrigin or TransformOrigin aren't explicitly set, with nested behavior available for customization. + +
    -
  • TransformOrigin = Origin.TopLeft - The transform origin point for the popover.
  • +
  • TransformOrigin = Origin.TopLeft: Transform origin point.
@@ -105,5 +108,5 @@
-
+
diff --git a/src/MudBlazor.Docs/Pages/Components/Select/SelectPage.razor b/src/MudBlazor.Docs/Pages/Components/Select/SelectPage.razor index 7062da04fcb1..56c7ab6557da 100644 --- a/src/MudBlazor.Docs/Pages/Components/Select/SelectPage.razor +++ b/src/MudBlazor.Docs/Pages/Components/Select/SelectPage.razor @@ -182,13 +182,11 @@ - This component uses MudPopover - to place its list of items in combination with MudOverlay. - Some of these settings can be controlled with DropdownSettings, - which contain defaults for the MudPopover appearance and behavior. - - Read more on popover's page. - + This component leverages MudPopover along with + MudOverlay to manage its item list. + You can configure certain aspects using DropdownSettings, + which sets default appearance and behavior properties. + Learn more on the popover documentation page. diff --git a/src/MudBlazor.Docs/Pages/Components/Tabs/Examples/TabsDragAndDropExample.razor b/src/MudBlazor.Docs/Pages/Components/Tabs/Examples/TabsDragAndDropExample.razor new file mode 100644 index 000000000000..df8444ed93b0 --- /dev/null +++ b/src/MudBlazor.Docs/Pages/Components/Tabs/Examples/TabsDragAndDropExample.razor @@ -0,0 +1,72 @@ +@namespace MudBlazor.Docs.Examples + + + + Tab One + + + Tab Two + + + Tab Three + + + Tab Four + " + + Tab Five + + + Tab Six + + + Tab Seven + + + Tab Eight + + + Tab Nine + + + + + Top + Start + Left + Right + End + Bottom + + +@code { + public Position Position { get; set; } = Position.Left; + public int TabCount { get; set; } = 6; + + private void OnSelectedValue(Position value) + { + switch (value) + { + case Position.Top: + Position = Position.Top; + break; + case Position.Start: + Position = Position.Start; + break; + case Position.Left: + Position = Position.Left; + break; + case Position.Right: + Position = Position.Right; + break; + case Position.End: + Position = Position.End; + break; + case Position.Bottom: + Position = Position.Bottom; + break; + } + } +} diff --git a/src/MudBlazor.Docs/Pages/Components/Tabs/TabsPage.razor b/src/MudBlazor.Docs/Pages/Components/Tabs/TabsPage.razor index b3032b5c7668..a577998c5acf 100644 --- a/src/MudBlazor.Docs/Pages/Components/Tabs/TabsPage.razor +++ b/src/MudBlazor.Docs/Pages/Components/Tabs/TabsPage.razor @@ -116,6 +116,19 @@
+ + + + Tab reordering can be enabled by setting EnableDragAndDrop to true. + When enabled, users can rearrange tabs by dragging them to new positions. This is useful for dynamic interfaces where the number or order of tabs may change. + The example below demonstrates drag-and-drop with a variable number of tabs that can be reordered interactively at various positions. + + + + + + + diff --git a/src/MudBlazor.Docs/Pages/Components/ToggleGroup/ToggleGroupPage.razor b/src/MudBlazor.Docs/Pages/Components/ToggleGroup/ToggleGroupPage.razor index b16a09138f66..9f172d01f49b 100644 --- a/src/MudBlazor.Docs/Pages/Components/ToggleGroup/ToggleGroupPage.razor +++ b/src/MudBlazor.Docs/Pages/Components/ToggleGroup/ToggleGroupPage.razor @@ -1,20 +1,20 @@ @page "/components/togglegroup" - - + + - The MudToggleGroup holds a number of MudToggleItems - and semantically groups them together into a selection. The items can be configured to show text or - custom content such as an icon. To show a check mark set the CheckMark parameter. - The check mark will push the text to the side by default. If you don't want this you can set - the FixedContent parameter to counterbalance the checkmark with padding on the right. + MudToggleGroup groups several MudToggleItem components together, allowing selection from multiple options. + Each item can display text, icons, or custom content. + + Use CheckMark to display a checkmark beside selected items. + If this pushes content too far, set FixedContent to balance spacing. + - If you don't set the Text property the items will simply show the - Value if you didn't define the child content of a MudToggleItem. + If Text isn't set, items default to displaying their Value, unless custom content is defined. @@ -24,20 +24,22 @@ - + - MudToggleGroup has three different selection modes: SelectionMode.SingleSelection, - SelectionMode.MultiSelection and SelectionMode.ToggleSelection. - Single selection is the default and allows only one choice to be selected at the same time, just like in a radio group. - With multi-selection many items can be selected at the same time or none. Toggle-selection is an exclusive single - selection which allows toggling off the selected value so that nothing is selected. + The MudToggleGroup offers three modes: + +
    +
  • SingleSelection (default): Only one item selected at a time.
  • +
  • MultiSelection: Multiple items or none can be selected.
  • +
  • ToggleSelection: Allows deselecting the current choice, resulting in no selection.
  • +
+ - You must set the Value property of each item to a unique value or the selection - won't work. Also, the type of the item's value must match the group's T parameter. + Ensure each item's Value is unique and matches the group's type parameter (T). + - Make sure to bind the toggle group's Value property in single- and toggle-selection mode - and Values with multi-selection. + Bind the group's Value (single/toggle selection) or Values (multi-selection) properties appropriately.
@@ -49,7 +51,7 @@ - You can customize the look of selected items with SelectedClass. + Customize the appearance of selected items using SelectedClass. @@ -60,10 +62,9 @@ - MudToggleItem supports customization of its content with a - RenderFragmentbool where the parameter conveys - whether or not the item is currently selected. In the example the context - variable was used to color the selected chip green. + Customize each MudToggleItem content using a RenderFragment<bool>, where the boolean parameter indicates selection state. + This allows dynamic styling, such as coloring selected items. + In the example the context variable was used to color the selected chip green. diff --git a/src/MudBlazor.Docs/Pages/Components/TreeView/TreeViewPage.razor b/src/MudBlazor.Docs/Pages/Components/TreeView/TreeViewPage.razor index e8c25370f384..f95efb7ef68a 100644 --- a/src/MudBlazor.Docs/Pages/Components/TreeView/TreeViewPage.razor +++ b/src/MudBlazor.Docs/Pages/Components/TreeView/TreeViewPage.razor @@ -1,20 +1,19 @@ @page "/components/treeview" - - An extensively customizable tree view component with selection and lazy-loading support. - + - + - The TreeView allows exploring of hierarchic data. In its simplest form it only displays text via the Text property of its items. - But you can also attach a value of type T to each item via the Value property. - In this example ReadOnly is set to true to prevent value selection. Some items in this example have a text, some have a value and one has both. If you use - only Text and T="string" then that text will also serve as value. If you set only Value - then the text will be derived from the value. You can of course set both to have different Text and Value. This will become important - for value selection (see Selection). +

+ The TreeView component is designed to display hierarchical data structures. By default, each MudTreeViewItem displays its Text property. +

+
+

+ You can also associate a data value with each item using the Value property. If a Value is not provided, the item's Text will be used as its value. This sample demonstrates a read-only implementation where items are defined with either text, a value, or both, illustrating the basic display capabilities of the component. +

@@ -25,14 +24,18 @@ - Hover applies a hover effect on mouse-over. Ripple applies a ripple effect on click, except if - ExpandOnDoubleClick is set. Dense will result in a more compact vertical padding of the item items to save space. - Disabled will prevent all interaction with any items. - With ExpandOnClick a subtree can be expanded and collapsed by clicking on it. With ExpandOnDoubleClick, - only a double-click will expand or collapse the subtrees. Additionally, a OnDoubleClick callback can be assigned to set a custom double click behaviour. - - Note that ExpandOnDoubleClick overrules ExpandOnClick if both are set. - +

+ The TreeView's appearance and behavior can be controlled with several parameters: +

+
    +
  • Hover: Applies a visual effect to an item on mouse-over.
  • +
  • Ripple: Enables a ripple effect on item click, unless ExpandOnDoubleClick is active.
  • +
  • Dense: Reduces the vertical padding of items for a more compact display.
  • +
  • Disabled: Prevents all user interaction with the TreeView.
  • +
  • ExpandOnClick: Allows expanding and collapsing of parent nodes with a single click.
  • +
  • ExpandOnDoubleClick: Restricts expand and collapse functionality to a double-click action. Note that this property overrides ExpandOnClick.
  • +
  • OnDoubleClick: This callback can be assigned to implement custom behavior for double-click events.
  • +
@@ -43,8 +46,13 @@ - The icons and their color can be changed individually per item via Icon and IconColor. This example uses a custom - ExpandButtonIcon and shows how to apply an alternative icon for expanded subtrees via the IconExpanded property. +

+ Enhance the visual presentation of TreeView items by assigning icons. The Icon and IconColor properties can be set for each MudTreeViewItem. +

+
+

+ This example defines a custom ExpandButtonIcon for parent nodes and provide an alternative icon for the expanded state via the IconExpanded property. +

@@ -55,11 +63,16 @@ - If you set SelectionMode to SelectionMode.SingleSelection you can select a single value from the entire tree. - SelectionMode.ToggleSelection is similar, except that it allows to deselect a previously selected value by clicking on it again. - You can use @@bind-SelectedValue on the MudTreeView to get updates about the selected value or to influence - the selected value like you can do in this example with the chip set. - The color of the selected item can be changed with Color property. +

+ To enable item selection, set the SelectionMode parameter: +

+
    +
  • SelectionMode.SingleSelection: Allows only one item to be selected at a time across the entire tree.
  • +
  • SelectionMode.ToggleSelection: Behaves like single selection, but allows a selected item to be deselected by clicking it again.
  • +
+

+ Use @@bind-SelectedValue to programmatically get or set the currently selected item. The active item's color is controlled by the Color property. +

@@ -70,10 +83,9 @@ - If you set SelectionMode to SelectionMode.MultiSelection you can select multiple values from the entire tree. - Use @@bind-SelectedValues (note the 's' at the end) on the MudTreeView to get updates about the selected values or to influence - the selection like you can do in this example with the chip set. - The color of the checkboxes can be changed with the CheckBoxColor property. + Set SelectionMode to SelectionMode.MultiSelection to allow users to select multiple items. Each selectable item will render a checkbox. + Use @@bind-SelectedValues to manage the collection of selected items. The color of the checkboxes can be customized with the CheckBoxColor property. + This example demonstrates how to bind the selection to a set of chips. @@ -84,9 +96,13 @@ - You can also bind the parameters Selected and Expanded on individual MudTreeViewItems. - Of course, manipulating the selection should typically happen via binding SelectedValue or SelectedValues on the - MudTreeView but binding the item's parameters makes sense when using the item template (see next example). +

+ You can directly bind the Selected and Expanded parameters on individual MudTreeViewItem components. +

+
+

+ While managing selection is typically done on the parent MudTreeView via @@bind-SelectedValue or @@bind-SelectedValues, direct item binding is particularly useful when creating custom interactions within an item template. +

@@ -97,9 +113,14 @@ - With AutoExpand set to true collapsed sub-trees that become selected will be expanded automatically. To test it, select chips and see how the corresponding - tree items will be expanded. - To expand or collapse all levels of the tree use the public members ExpandAll() and CollapseAll(). +

+ When AutoExpand is set to true, any collapsed parent node that contains a selected item will automatically expand to reveal it. + This is useful for ensuring the context of a programmatically selected item is visible to the user. +

+
+

+ To expand or collapse all nodes at once, you can call the public ExpandAll() and CollapseAll() methods on the TreeView instance. +

@@ -109,7 +130,10 @@ - This example shows how to use ItemTemplate to automatically build the tree items according to a hierarchical data structure. + + Use the ItemTemplate to dynamically generate TreeView items from a hierarchical data source. + This approach simplifies rendering complex or data-driven trees by defining a template for each item's structure and its children. + @@ -117,10 +141,17 @@ - + - Data can be loaded on demand with the use of the ServerData parameter, the loading icon and its color can be changed with LoadingIcon and LoadingIconColor prop. - Lazy-loading can also be disabled for certain items with the CanExpand property, if you know that there are is no subtree. +

+ For large datasets, you can load data on-demand using ServerData. + This function is invoked when a user expands a node, allowing efficient lazy-loading of child items. +

+
+

+ The loading indicator's icon and color can be customized with LoadingIcon and LoadingIconColor. + If you know a specific item has no children and should not trigger a server call, you can prevent the loading behavior by setting its CanExpand property to false. +

@@ -131,8 +162,14 @@ - The tree nodes can be filtered using the FilterFunc. The filtering can be triggered using the Filter function on the MudTreeView component. - This function is applied to every tree view item in the tree and sets the Visible property. +

+ The TreeView nodes can be filtered using the Filter function, which accepts a custom filter delegate. + This function is applied to every item, setting its Visible property based on whether it matches the filter criteria. +

+
+

+ This example demonstrates how to implement a text-based filter that dynamically shows or hides nodes based on user input. +

@@ -143,12 +180,16 @@ - When the Content property is used, it will completely replace the default rendering of the MudTreeViewItem to use your own. - This gives you every opportunity to change the behavior of MudTreeView to anything you want. In this example we build our own non-standard multi selection behavior where - selecting the parent node does not automatically select the children and vice-versa. Also, note how only certain items can be selected. -

- By the way, to get nice scrolling behavior like in this example, you must constrain the container's height or max-height and set the container's - overflow-y accordingly. +

+ For full control over rendering and behavior, use the Content render fragment. + This replaces the default MudTreeViewItem markup with your own custom implementation. +

+
+

+ This example demonstrates creating a non-standard multi-selection behavior where selecting a parent node does not affect its children. + It also shows how to make only specific items selectable. + To enable scrolling, as seen in this sample, constrain the container's height or max-height and set overflow-y to auto or scroll. +

@@ -159,8 +200,8 @@ - Use the BodyContent instead of the Content render fragment - if you want to customize the tree item but still use the built-in icons and expansion buttons. + Use the BodyContent render fragment when you want to customize the main content area of a tree item but retain the built-in icons, indentation, and expansion buttons. + This provides a balance between customization and leveraging the default item structure. @@ -170,4 +211,3 @@
- diff --git a/src/MudBlazor.Docs/Pages/Getting Started/Installation/Examples/InstallationManualCssFontsExample.razor b/src/MudBlazor.Docs/Pages/Getting Started/Installation/Examples/InstallationManualCssFontsExample.razor index 13f4a8281e29..80e4607a7ae9 100644 --- a/src/MudBlazor.Docs/Pages/Getting Started/Installation/Examples/InstallationManualCssFontsExample.razor +++ b/src/MudBlazor.Docs/Pages/Getting Started/Installation/Examples/InstallationManualCssFontsExample.razor @@ -1,4 +1,4 @@ @namespace MudBlazor.Docs.Examples - \ No newline at end of file + diff --git a/src/MudBlazor.Docs/Pages/Getting Started/Installation/Examples/InstallationManualCssFontsExampleNet8.razor b/src/MudBlazor.Docs/Pages/Getting Started/Installation/Examples/InstallationManualCssFontsExampleNet8.razor new file mode 100644 index 000000000000..0848058b717b --- /dev/null +++ b/src/MudBlazor.Docs/Pages/Getting Started/Installation/Examples/InstallationManualCssFontsExampleNet8.razor @@ -0,0 +1,4 @@ +@namespace MudBlazor.Docs.Examples + + + diff --git a/src/MudBlazor.Docs/Pages/Getting Started/Installation/Examples/InstallationManualCssFontsExampleWasmStandalone.razor b/src/MudBlazor.Docs/Pages/Getting Started/Installation/Examples/InstallationManualCssFontsExampleWasmStandalone.razor new file mode 100644 index 000000000000..d6b01d9dd80c --- /dev/null +++ b/src/MudBlazor.Docs/Pages/Getting Started/Installation/Examples/InstallationManualCssFontsExampleWasmStandalone.razor @@ -0,0 +1,5 @@ +@namespace MudBlazor.Docs.Examples + + + + diff --git a/src/MudBlazor.Docs/Pages/Getting Started/Installation/Installation.razor b/src/MudBlazor.Docs/Pages/Getting Started/Installation/Installation.razor index 51f1b7fb3916..a9249be2c55d 100644 --- a/src/MudBlazor.Docs/Pages/Getting Started/Installation/Installation.razor +++ b/src/MudBlazor.Docs/Pages/Getting Started/Installation/Installation.razor @@ -65,11 +65,34 @@ Add the following to your HTML head section, it's either index.html or _Layout.cshtml/_Host.cshtml/App.razor depending on whether you're running WebAssembly or Server. - + Next, add the MudBlazor js file next to the default Blazor script at the end: - + + + + On .NET 9 or later, ensure that the app.MapStaticAssets() middleware + is enabled so that @@Assets[""] can fingerprint the files. When upgrading from + an earlier .NET release this does not happen automatically. + + + + + + Please make sure you've implemented a suitable cache busting strategy for all scripts and stylesheets as seen above. + Otherwise your users will experience caching related issues when you update MudBlazor. + + +
diff --git a/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualCode.html b/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualCode.html index 391e3d24b378..9e7304a5f2d5 100644 --- a/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualCode.html +++ b/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualCode.html @@ -1,7 +1,7 @@ 
-<script src="_content/MudBlazor/MudBlazor.min.js"></script>
+<script src="@Assets["_content/MudBlazor/MudBlazor.min.js"]"></script>
 
\ No newline at end of file diff --git a/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualNet8.razor b/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualNet8.razor new file mode 100644 index 000000000000..27bab7f52183 --- /dev/null +++ b/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualNet8.razor @@ -0,0 +1,3 @@ +@namespace MudBlazor.Docs.Examples + +@* Dummy file do not remove, needed for content section *@ \ No newline at end of file diff --git a/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualNet8Code.html b/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualNet8Code.html new file mode 100644 index 000000000000..7ee7eed40368 --- /dev/null +++ b/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualNet8Code.html @@ -0,0 +1,7 @@ +
+
+
+<script src="_content/MudBlazor/MudBlazor.min.css?v=@(MudBlazor.Metadata.Version)"></script>
+
+
+
\ No newline at end of file diff --git a/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualWasmStandalone.razor b/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualWasmStandalone.razor new file mode 100644 index 000000000000..27bab7f52183 --- /dev/null +++ b/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualWasmStandalone.razor @@ -0,0 +1,3 @@ +@namespace MudBlazor.Docs.Examples + +@* Dummy file do not remove, needed for content section *@ \ No newline at end of file diff --git a/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualWasmStandaloneCode.html b/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualWasmStandaloneCode.html new file mode 100644 index 000000000000..a6ca822456bd --- /dev/null +++ b/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualWasmStandaloneCode.html @@ -0,0 +1,8 @@ +
+
+
+<!-- Important: Increment the version parameter whenever you update MudBlazor to prevent caching issues -->
+<script src="_content/MudBlazor/MudBlazor.min.css?v=1"></script>
+
+
+
\ No newline at end of file diff --git a/src/MudBlazor.Docs/wwwroot/CommunityExtensions.json b/src/MudBlazor.Docs/wwwroot/CommunityExtensions.json index 8fb1c64f1ada..8a23b315c92c 100644 --- a/src/MudBlazor.Docs/wwwroot/CommunityExtensions.json +++ b/src/MudBlazor.Docs/wwwroot/CommunityExtensions.json @@ -116,5 +116,14 @@ "Link": "https://mudpdf.info", "GitHubUserPath": "tgothorp", "GitHubRepoPath": "MudBlazor.PdfViewer" + }, + { + "AvatarImageSrc": "https://raw.githubusercontent.com/phmatray/FormCraft/refs/heads/main/FormCraft/icon.png", + "Category": "Utility", + "Name": "FormCraft", + "Description": "A powerful, type-safe dynamic form library for Blazor applications with fluent API design. Provides seamless integration with MudBlazor components, enabling developers to build complex forms with minimal code.", + "Link": "https://phmatray.github.io/FormCraft", + "GitHubUserPath": "phmatray", + "GitHubRepoPath": "FormCraft" } ] diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridGroupExpandedTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridGroupExpandedTest.razor index e96e1dfbd56b..7e4b42f7ac6d 100644 --- a/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridGroupExpandedTest.razor +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridGroupExpandedTest.razor @@ -1,4 +1,4 @@ - Fruits @@ -7,7 +7,7 @@ GroupExpanded="true" RowContextMenuClick="@OnRowContextMenuClick"> - + Category: @context.Grouping.Key @@ -19,9 +19,11 @@ GroupExpanded="true" RowContextMenuClick="@OnRowContextMenuClick"> Expand All Collapse All Add Fruit + Toggle Groupable @code { + private bool _groupable; private MudDataGrid _dataGrid = null!; private readonly List _fruits = [ @@ -31,8 +33,17 @@ GroupExpanded="true" RowContextMenuClick="@OnRowContextMenuClick"> ]; private readonly Func _groupBy = x => x.Category; + [Parameter] + public bool Groupable { get; set; } = true; + public bool RowContextMenuClicked { get; set; } + protected override void OnParametersSet() + { + base.OnParametersSet(); + _groupable = Groupable; + } + public void AddFruit() { _fruits.Add(new Fruit("Banana", 5, "Musa")); diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridGroupingMultiLevelTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridGroupingMultiLevelTest.razor index eb88375c9738..0217dd5864bc 100644 --- a/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridGroupingMultiLevelTest.razor +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/DataGrid/DataGridGroupingMultiLevelTest.razor @@ -15,9 +15,18 @@ @bind-Value="_searchString" @bind-Value:after="@(() => _dataGrid.ReloadServerData())" DebounceInterval="100" Variant="Variant.Outlined" Clearable /> + + + @context.Title: @context.Grouping.Key Count: @context.Grouping.Count() + @if (context.DataGrid.FilteredItems.Count() != 0) + { + @string.Format(" Percentage: {0:P1}", context.Grouping.Count() / ((double)context.DataGrid.FilteredItems.Count())) + } + + - + +
Select Template @@ -8,4 +8,7 @@ @code { public IReadOnlyList? Files { get; private set; } + + [Parameter] + public long? MaxFileSize { get; set; } } diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/FileUpload/FileUploadSingleFileTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/FileUpload/FileUploadSingleFileTest.razor new file mode 100644 index 000000000000..d7cdd96aade1 --- /dev/null +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/FileUpload/FileUploadSingleFileTest.razor @@ -0,0 +1,14 @@ + + +
+ Select Template +
+
+
+ +@code { + public IBrowserFile? File { get; private set; } + + [Parameter] + public long? MaxFileSize { get; set; } +} diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Menu/MenuFlipTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Menu/MenuFlipTest.razor new file mode 100644 index 000000000000..9a8d80c480f0 --- /dev/null +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Menu/MenuFlipTest.razor @@ -0,0 +1,45 @@ + + + @for (int i = 0; i < 20; i++) + { + + } + + + + + + + + + + + + + + + + + + + + + +@code { + private MudMenu _contextMenu = null!; + private readonly DropdownSettings _dropdownSettings = new DropdownSettings() { OverflowBehavior = OverflowBehavior.FlipAlways }; + + private async Task ListItem_Clicked(MouseEventArgs e) + { + if (_contextMenu != null) + { + await _contextMenu.OpenMenuAsync(e); + } + } +} diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Table/TablePagerInfoTextTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Table/TablePagerInfoTextTest1.razor similarity index 100% rename from src/MudBlazor.UnitTests.Viewer/TestComponents/Table/TablePagerInfoTextTest.razor rename to src/MudBlazor.UnitTests.Viewer/TestComponents/Table/TablePagerInfoTextTest1.razor diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Table/TablePagerInfoTextTest2.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Table/TablePagerInfoTextTest2.razor new file mode 100644 index 000000000000..b6d822a3f871 --- /dev/null +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Table/TablePagerInfoTextTest2.razor @@ -0,0 +1,22 @@ +@using System.ComponentModel + + + + @context + + + + + + +@code { + public static string __description__ = "Info in pager should be correctly formated"; + + [Parameter] + public string? InfoFormat { get; set; } + + private readonly string[] _items = + [ + "A", "B", "C" + ]; +} diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Tabs/TabsDragAndDropTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Tabs/TabsDragAndDropTest.razor new file mode 100644 index 000000000000..298962142a1c --- /dev/null +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Tabs/TabsDragAndDropTest.razor @@ -0,0 +1,18 @@ + + + Content One + + + Content Two + + + Content Three + + + Content Disabled + + + +@code { + public static string __description__ = "Viewer for MudTabs. Drag and drop tabs to reorder them."; +} diff --git a/src/MudBlazor.UnitTests/Components/AutocompleteTests.cs b/src/MudBlazor.UnitTests/Components/AutocompleteTests.cs index eca2b66399b0..926cbfaac0a0 100644 --- a/src/MudBlazor.UnitTests/Components/AutocompleteTests.cs +++ b/src/MudBlazor.UnitTests/Components/AutocompleteTests.cs @@ -1491,7 +1491,7 @@ public async Task AutocompleteStrictFalseTest(int index) var items = comp.FindComponents>().ToArray(); items.Length.Should().Be(10); var item = items.SingleOrDefault(x => x.Markup.Contains(californiaString)); - items.ToList().IndexOf(item).Should().Be(5); + items.ToList().IndexOf(item).Should().Be(4); comp.WaitForAssertion(() => items.Single(s => s.Markup.Contains(californiaString)).Find(listItemQuerySelector).ClassList.Should().Contain(selectedItemClassName)); await comp.InvokeAsync(async () => await autocompleteComponent.Find("input").KeyUpAsync(new KeyboardEventArgs() { Key = "Escape" })); // Close autocomplete. @@ -1503,14 +1503,17 @@ public async Task AutocompleteStrictFalseTest(int index) autocomplete.Text.Should().Be(virginiaString); autocomplete.Value.StateName.Should().Be(virginiaString); - //West Virginia is not in the first 10 states, so it should not appear in the list await comp.InvokeAsync(autocompleteComponent.Instance.OpenMenuAsync); // reopen menu because Enter closes it. comp.WaitForAssertion(() => comp.FindAll("div.mud-popover")[index].ClassList.Should().Contain("mud-popover-open")); var items2 = comp.FindComponents>().ToArray(); items2.Length.Should().Be(10); - var item2 = items2.SingleOrDefault(x => x.Markup.Contains(virginiaString)); - items2.ToList().IndexOf(item).Should().Be(-1); - items2.Count(s => s.Find(listItemQuerySelector).ClassList.Contains(selectedItemClassName)).Should().Be(0); + // Select Virginia + var item2 = items2.FirstOrDefault(x => x.Markup.Contains(virginiaString)); + // Virginia and West Virginia should be in the list + var count = items2.Count(x => x.Markup.Contains(virginiaString)); + count.Should().Be(2); + items2.ToList().IndexOf(item2).Should().Be(5); + items2.Count(s => s.Find(listItemQuerySelector).ClassList.Contains(selectedItemClassName)).Should().Be(1); } [Test] diff --git a/src/MudBlazor.UnitTests/Components/ColorPickerTests.cs b/src/MudBlazor.UnitTests/Components/ColorPickerTests.cs index 88a1503617fa..201a65975a02 100644 --- a/src/MudBlazor.UnitTests/Components/ColorPickerTests.cs +++ b/src/MudBlazor.UnitTests/Components/ColorPickerTests.cs @@ -133,11 +133,11 @@ private IHtmlInputElement[] GetColorInputs(IRenderedComponent comp, int index, int expectedCount = 4) => GetColorInputs(comp, expectedCount)[index]; [Test] - public void ColorPickerOpenButtonAriaLabel() + public void ColorPickerOpenButtonDefaultAriaLabel() { var comp = Context.RenderComponent(); var openButton = comp.Find(".mud-input-adornment button"); - openButton.Attributes.GetNamedItem("aria-label")?.Value.Should().Be("Open Color Picker"); + openButton.Attributes.GetNamedItem("aria-label")?.Value.Should().Be("Open"); } [Test] diff --git a/src/MudBlazor.UnitTests/Components/DataGridGroupingTests.cs b/src/MudBlazor.UnitTests/Components/DataGridGroupingTests.cs index 145911632c0d..4273f0c13218 100644 --- a/src/MudBlazor.UnitTests/Components/DataGridGroupingTests.cs +++ b/src/MudBlazor.UnitTests/Components/DataGridGroupingTests.cs @@ -579,10 +579,7 @@ public async Task DataGrid_Grouping_TestGroupableSets() // grouping shouldn't exist dataGrid.Instance._groupDefinition.Should().BeNull(); - foreach (var column in dataGrid.Instance.RenderedColumns) - { - column.GroupingState.Value.Should().Be(false); - } + // leaving grouping intact // no grouping rows var rows = component.FindComponents>(); @@ -622,6 +619,39 @@ public async Task DataGrid_Grouping_GroupDefinition() row.Instance.Items.Count().Should().Be(2); } + [Test] + public void DataGrid_IsGrouping() + { + // Tests the IsGrouping property of MudDataGrid to ensure it handles a change properly + // and ensures the correct UI is rendered for column options + var provider = Context.RenderComponent(); + var comp = Context.RenderComponent(); + var dataGrid = comp.FindComponent>(); + provider.Should().NotBeNull(); + comp.WaitForAssertion(() => comp.FindAll("tbody .mud-table-row").Count.Should().Be(7)); + var menus = comp.FindAll("span.column-options button[aria-label='Column options']"); + menus.Count.Should().Be(3); // one for each column + var countMenu = menus[1]; // 2nd column options (Count) + countMenu.Click(); + // Default is DataGrid Groupable = true and no Groupable override on column so should be groupable + provider.Markup.Should().Contain("Group"); + var overlay = provider.Find(".mud-overlay"); + overlay.Click(); // close the menu + + comp.SetParametersAndRender(x => x.Add(x => x.Groupable, false)); + // no change in grid rows since Grouping did not change + comp.WaitForAssertion(() => comp.FindAll("tbody .mud-table-row").Count.Should().Be(7)); + + menus = comp.FindAll("span.column-options button[aria-label='Column options']"); + menus.Count.Should().Be(3); // one for each column + countMenu = menus[1]; // 2nd column options (Count) + countMenu.Click(); + // DataGrid Groupable now = false and no Groupable override on column so should not be groupable + provider.Markup.Should().NotContain("Group"); + overlay = provider.Find(".mud-overlay"); + overlay.Click(); // close the menu + } + // https://github.com/MudBlazor/MudBlazor/pull/10213 // Allow grouping by null valus and toggle grouping keeps initial state on other groups [Test] @@ -645,5 +675,49 @@ public void DataGrid_Grouping_ByNull() expander.Click(); comp.FindAll("tbody .mud-table-row").Count.Should().Be(8); } + + [Test] + public async Task DataGridGroupingTemplateSetAtGridLevel() + { + var component = Context.RenderComponent(); + + var dataGrid = component.FindComponent>(); + await component.InvokeAsync(() => dataGrid.Instance.ReloadServerData()); + + //click to customize the group template + var groupingTemplateSwitch = component.FindComponent>(); + groupingTemplateSwitch.Find("input").Change(true); + dataGrid.Render(); + + //current grouping should use the defined template + var groupRow = component.FindComponent>().Find(".mud-datagrid-group"); + var text = new string(groupRow.TextContent.Where(c => !Char.IsWhiteSpace(c)).ToArray()); + text.Should().Be("Manufacturing1states"); + + //clear grouping + foreach (var col in dataGrid.Instance.RenderedColumns.Where(x => x.GroupingState.Value)) + { + await component.InvokeAsync(() => col.RemoveGrouping()); + } + //group by column with no grouptemplate + await component.InvokeAsync(() => dataGrid.Instance.RenderedColumns.Where(x => x.Title == nameof(DataGridGroupingMultiLevelTest.USState.Counties)).Single().SetGroupingAsync(true)); + dataGrid.Render(); + //grouping should be the template defined at grid level + groupRow = component.FindComponent>().Find(".mud-datagrid-group"); + text = new string(groupRow.TextContent.Where(c => !Char.IsWhiteSpace(c)).ToArray()); + text.Should().Be("Counties:67Count:2Percentage:20.0%"); + } + + [Test] + public async Task DataGridGroupingTemplateDefault() + { + await Task.Yield(); + var component = Context.RenderComponent(); + + //grouping should be the built in default + var groupRow = component.FindComponent>().Find(".mud-datagrid-group"); + var text = new string(groupRow.TextContent.Where(c => !Char.IsWhiteSpace(c)).ToArray()); + text.Should().Be("Name:John"); + } } } diff --git a/src/MudBlazor.UnitTests/Components/DataGridTests.cs b/src/MudBlazor.UnitTests/Components/DataGridTests.cs index 02f5082b1da5..bb8c2b1a3fb0 100644 --- a/src/MudBlazor.UnitTests/Components/DataGridTests.cs +++ b/src/MudBlazor.UnitTests/Components/DataGridTests.cs @@ -4316,10 +4316,10 @@ public void DataGridEditFormDialogIsCustomizableTest() //check if dialog is open comp.FindAll("div.mud-dialog-container").Should().NotBeEmpty(); //find button with arialabel close in dialog - var closeButton = comp.Find("button[aria-label=\"Close dialog\"]"); + var closeButton = comp.Find("button[aria-label=\"Close\"]"); closeButton.Should().NotBeNull(); //click close button - comp.Find("button[aria-label=\"Close dialog\"]").Click(); + comp.Find("button[aria-label=\"Close\"]").Click(); //check if dialog is closed comp.FindAll("div.mud-dialog-container").Should().BeEmpty(); } diff --git a/src/MudBlazor.UnitTests/Components/DatePickerTests.cs b/src/MudBlazor.UnitTests/Components/DatePickerTests.cs index 9a6d4457715e..614d07b201e0 100644 --- a/src/MudBlazor.UnitTests/Components/DatePickerTests.cs +++ b/src/MudBlazor.UnitTests/Components/DatePickerTests.cs @@ -41,11 +41,11 @@ public void Default() } [Test] - public void DatePickerOpenButtonAriaLabel() + public void DatePickerOpenButtonDefaultAriaLabel() { var comp = Context.RenderComponent(); var openButton = comp.Find(".mud-input-adornment button"); - openButton.Attributes.GetNamedItem("aria-label")?.Value.Should().Be("Open Date Picker"); + openButton.Attributes.GetNamedItem("aria-label")?.Value.Should().Be("Open"); } [Test] diff --git a/src/MudBlazor.UnitTests/Components/DateRangePickerTests.cs b/src/MudBlazor.UnitTests/Components/DateRangePickerTests.cs index bb4033aa946b..55c3ba85e2ec 100644 --- a/src/MudBlazor.UnitTests/Components/DateRangePickerTests.cs +++ b/src/MudBlazor.UnitTests/Components/DateRangePickerTests.cs @@ -80,11 +80,11 @@ public void DateRangePickerSeparatorIcon() } [Test] - public void DateRangePickerOpenButtonAriaLabel() + public void DateRangePickerOpenButtonDefaultAriaLabel() { var comp = Context.RenderComponent(); var openButton = comp.Find(".mud-input-adornment button"); - openButton.Attributes.GetNamedItem("aria-label")?.Value.Should().Be("Open Date Range Picker"); + openButton.Attributes.GetNamedItem("aria-label")?.Value.Should().Be("Open"); } [Test] diff --git a/src/MudBlazor.UnitTests/Components/FileUploadTests.cs b/src/MudBlazor.UnitTests/Components/FileUploadTests.cs index da94107aa2ec..89a48d50ff75 100644 --- a/src/MudBlazor.UnitTests/Components/FileUploadTests.cs +++ b/src/MudBlazor.UnitTests/Components/FileUploadTests.cs @@ -2,22 +2,14 @@ // MudBlazor licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; using System.Globalization; -using System.Threading; -using System.Threading.Tasks; -using AngleSharp.Html.Dom; using Bunit; using FluentAssertions; -using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Forms; -using Microsoft.AspNetCore.Components.Web; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using MudBlazor.UnitTests.Dummy; using MudBlazor.UnitTests.Mocks; -using MudBlazor.UnitTests.TestComponents; using MudBlazor.UnitTests.TestComponents.FileUpload; using NUnit.Framework; @@ -428,5 +420,205 @@ public async Task Should_trigger_file_change_callbacks_as_expected() comp.Instance.FilesChangedCount.Should().Be(2); comp.Instance.OnFilesChangedCount.Should().Be(2); } + + private static InputFileContent CreateDummyFile(string fileName, long size) + { + var content = new byte[size]; + var file = new DummyBrowserFile(fileName, DateTimeOffset.Now, size, "application/octet-stream", content); + + return InputFileContent.CreateFromBinary(file.Content, file.Name, null, file.ContentType); + } + + [Test] + public void MaxFileSize_SingleFile_WithinLimit() + { + var comp = Context.RenderComponent(parameters => parameters.Add(p => p.MaxFileSize, 100L)); + + var file = CreateDummyFile("test.txt", 50); + var input = comp.FindComponent(); + + input.UploadFiles(file); + + comp.Instance.File.Should().NotBeNull(); + comp.Instance.File.Name.Should().Be("test.txt"); + comp.Instance.File.Size.Should().Be(50); + } + + [Test] + public void MaxFileSize_SingleFile_ExceedsLimit() + { + var comp = Context.RenderComponent(parameters => parameters.Add(p => p.MaxFileSize, 100L)); + + var file = CreateDummyFile("test.txt", 150); + var input = comp.FindComponent(); + var fileUpload = comp.FindComponent>().Instance; + + input.UploadFiles(file); + + comp.Instance.File.Should().BeNull(); // File should be rejected + fileUpload.Error.Should().BeTrue(); + fileUpload.ErrorText.Should().Be("File 'test.txt' exceeds the maximum allowed size of 100 bytes."); + } + + [Test] + public void MaxFileSize_SingleFile_NoLimit() + { + var comp = Context.RenderComponent(parameters => parameters.Add(p => p.MaxFileSize, null)); + + var file = CreateDummyFile("test.txt", 200); + var input = comp.FindComponent(); + var fileUpload = comp.FindComponent>().Instance; + + input.UploadFiles(file); + + comp.Instance.File.Should().NotBeNull(); + comp.Instance.File.Name.Should().Be("test.txt"); + comp.Instance.File.Size.Should().Be(200); + fileUpload.Error.Should().BeFalse(); + fileUpload.ErrorText.Should().BeNullOrEmpty(); + } + + [Test] + public void MaxFileSize_MultipleFiles_AllWithinLimit() + { + var comp = Context.RenderComponent(parameters => parameters.Add(p => p.MaxFileSize, 100L)); + + var file1 = CreateDummyFile("test1.txt", 50); + var file2 = CreateDummyFile("test2.txt", 70); + + var input = comp.FindComponent(); + var fileUpload = comp.FindComponent>>().Instance; + + input.UploadFiles(file1, file2); + + comp.Instance.Files.Should().NotBeNull(); + comp.Instance.Files.Count.Should().Be(2); + comp.Instance.Files[0].Name.Should().Be("test1.txt"); + comp.Instance.Files[1].Name.Should().Be("test2.txt"); + fileUpload.Error.Should().BeFalse(); + fileUpload.ErrorText.Should().BeNullOrEmpty(); + } + + [Test] + public void MaxFileSize_MultipleFiles_SomeExceedLimit() + { + var comp = Context.RenderComponent(parameters => parameters.Add(p => p.MaxFileSize, 100L)); + + var file1 = CreateDummyFile("test1.txt", 50); + var file2 = CreateDummyFile("test2.txt", 120); + var file3 = CreateDummyFile("test3.txt", 70); + + var input = comp.FindComponent(); + var fileUpload = comp.FindComponent>>().Instance; + + input.UploadFiles(file1, file2, file3); + + // Assertions after OnChangeAsync + comp.Instance.Files.Should().NotBeNull(); + comp.Instance.Files.Count.Should().Be(2); + comp.Instance.Files.Should().Contain(f => f.Name == "test1.txt"); + comp.Instance.Files.Should().Contain(f => f.Name == "test3.txt"); + fileUpload.Error.Should().BeTrue(); + fileUpload.ErrorText.Should().Be("File 'test2.txt' exceeds the maximum allowed size of 100 bytes."); + } + + [Test] + public void MaxFileSize_MultipleFiles_AllExceedLimit() + { + var comp = Context.RenderComponent(parameters => parameters.Add(p => p.MaxFileSize, 100L)); + + var file1 = CreateDummyFile("test1.txt", 120); + var file2 = CreateDummyFile("test2.txt", 150); + + var input = comp.FindComponent(); + + input.UploadFiles(file1, file2); + var fileUpload = comp.FindComponent>>().Instance; + + comp.Instance.Files.Should().NotBeNull(); // It will be an empty list + comp.Instance.Files.Count.Should().Be(0); + fileUpload.Error.Should().BeTrue(); + + var validationErrors = fileUpload.ValidationErrors; + validationErrors.Should().HaveCount(2); + validationErrors.Should().Contain("File 'test1.txt' exceeds the maximum allowed size of 100 bytes."); + validationErrors.Should().Contain("File 'test2.txt' exceeds the maximum allowed size of 100 bytes."); + } + + [Test] + public void MaxFileSize_MultipleFiles_NoLimit() + { + var comp = Context.RenderComponent(parameters => parameters.Add(p => p.MaxFileSize, null)); + + var file1 = CreateDummyFile("test1.txt", 200); + var file2 = CreateDummyFile("test2.txt", 300); + + var input = comp.FindComponent(); + var fileUpload = comp.FindComponent>>().Instance; + + input.UploadFiles(file1, file2); + + comp.Instance.Files.Should().NotBeNull(); + comp.Instance.Files.Count.Should().Be(2); + comp.Instance.Files[0].Name.Should().Be("test1.txt"); + comp.Instance.Files[1].Name.Should().Be("test2.txt"); + fileUpload.Error.Should().BeFalse(); + fileUpload.ErrorText.Should().BeNullOrEmpty(); + } + + [Test] + public async Task MaxFileSize_ClearValidationAfterError() + { + var comp = Context.RenderComponent(parameters => parameters.Add(p => p.MaxFileSize, 100)); + + var file1 = CreateDummyFile("test1.txt", 200); + var file2 = CreateDummyFile("test2.txt", 300); + + var input = comp.FindComponent(); + + input.UploadFiles(file1, file2); + + // Assert initial error state + comp.Instance.Files.Should().BeEmpty(); + + var fileUpload = comp.FindComponent>>().Instance; + + fileUpload.Error.Should().BeTrue(); + fileUpload.ErrorText.Should().Be("File 'test1.txt' exceeds the maximum allowed size of 100 bytes."); + + await comp.InvokeAsync(fileUpload.ClearAsync); + + // Assert cleared state + comp.Instance.Files.Should().BeNull(); + fileUpload.Error.Should().BeFalse(); // Errors should be cleared + fileUpload.ErrorText.Should().BeNullOrEmpty(); // ErrorText should be cleared + fileUpload.ValidationErrors.Should().BeEmpty(); // ValidationErrors related to MaxFileSize should be cleared + } + + [Test] + public async Task MaxFileSize_ResetValidationAfterError() + { + var comp = Context.RenderComponent(parameters => parameters.Add(p => p.MaxFileSize, 100)); + + var file1 = CreateDummyFile("test1.txt", 200); + + var input = comp.FindComponent(); + var fileUpload = comp.FindComponent>().Instance; + + input.UploadFiles(file1); + + // Assert initial error state + comp.Instance.File.Should().BeNull(); + fileUpload.Error.Should().BeTrue(); + fileUpload.ErrorText.Should().Be("File 'test1.txt' exceeds the maximum allowed size of 100 bytes."); + + await comp.InvokeAsync(fileUpload.ResetValidation); + + // Assert cleared state + comp.Instance.File.Should().BeNull(); + fileUpload.Error.Should().BeFalse(); // Errors should be cleared + fileUpload.ErrorText.Should().BeNullOrEmpty(); // ErrorText should be cleared + fileUpload.ValidationErrors.Should().BeEmpty(); // ValidationErrors related to MaxFileSize should be cleared + } } } diff --git a/src/MudBlazor.UnitTests/Components/MenuTests.cs b/src/MudBlazor.UnitTests/Components/MenuTests.cs index 6b98f3fb4e5f..68e5dc8e025e 100644 --- a/src/MudBlazor.UnitTests/Components/MenuTests.cs +++ b/src/MudBlazor.UnitTests/Components/MenuTests.cs @@ -434,7 +434,7 @@ public async Task OpenMenuAsync_Should_Set_FixedPosition() popover.ClassList.Should().Contain("mud-popover-anchor-top-left"); popover.ClassList.Should().Contain("mud-popover-position-override"); - popover.OuterHtml.Should().Contain("top:0px;left:0px;"); + popover.OuterHtml.Should().Contain("data-pc-x=\"0\" data-pc-y=\"0\""); await Context.Renderer.Dispatcher.InvokeAsync(mudMenuContext.CloseMenuAsync); } @@ -697,53 +697,6 @@ public async Task Menu_PointerEvents_ShowHide_WithDebounce() comp.FindAll("div.mud-popover-open").Count.Should().Be(1, "Submenu should close after full hide delay (2x hover delay)"); } - [Test] - public async Task Menu_PointerEvents_Cancellation() - { - // This method uses CatchAndLog to allow async events to run syncronously so we can test timing - // Set a predictable hover delay for testing - var hoverDelay = 300; - MudGlobal.MenuDefaults.HoverDelay = hoverDelay; - - var comp = Context.RenderComponent(); - - // Open the main menu first - comp.Find("button:contains('1')").Click(); - comp.FindAll("div.mud-popover-open").Count.Should().Be(1, "Main menu should be open"); - - // 1. Test cancellation of SHOW debounce - - var menus = comp.FindComponents(); - var menu = menus.FirstOrDefault(x => x.Instance.Label == "1.3").Instance; - menu.Should().NotBeNull(); - - // Start hover, then leave before debounce completes - menu.PointerEnterAsync(new PointerEventArgs()).CatchAndLog(); - await Task.Delay(50); - menu._showDebouncer.Cancel(); - - // Wait for original show debounce to have completed - await Task.Delay(hoverDelay + 100); - comp.FindAll("div.mud-popover-open").Count.Should().Be(1, - "Submenu should not open because show debounce was cancelled by pointer leave"); - - // 2. Test cancellation of HIDE debounce - - // First, open the submenu properly, use await since we are testing this - await menu.PointerEnterAsync(new PointerEventArgs()); - comp.FindAll("div.mud-popover-open").Count.Should().Be(2, "Submenu should be open"); - - // Start leave sequence, but re-enter before hide completes - menu.PointerLeaveAsync(new PointerEventArgs()).CatchAndLog(); - await Task.Delay(hoverDelay / 2); - menu._hideDebouncer.Cancel(); - - // Wait for what would have been the full hide delay - await Task.Delay(hoverDelay + 100); - comp.FindAll("div.mud-popover-open").Count.Should().Be(2, - "Submenu should still be open because hide debounce was cancelled by re-entering"); - } - [Test] public async Task Menu_PointerEvents_MultipleLevels() { diff --git a/src/MudBlazor.UnitTests/Components/TableTests.cs b/src/MudBlazor.UnitTests/Components/TableTests.cs index bc3fc0989982..4f6caf9229b8 100644 --- a/src/MudBlazor.UnitTests/Components/TableTests.cs +++ b/src/MudBlazor.UnitTests/Components/TableTests.cs @@ -2210,10 +2210,10 @@ public void ExpandAndCollapseAllGroupsTest() /// Tests the correct output when filter does not return any matching elements /// [Test] - public void TablePagerInfoTextTest() + public void TablePagerInfoTextTest1() { // create the component - var tableComponent = Context.RenderComponent(); + var tableComponent = Context.RenderComponent(); // print the generated html @@ -2241,6 +2241,23 @@ public void TablePagerInfoTextTest() tableComponent.Find("div.mud-table-page-number-information").Text().Should().Be("0-0 of 0", "'ZZZ' filter applied."); } + /// + /// Tests the correct output when custom info format provided + /// + [Test] + [TestCase("", "1-3 of 3")] + [TestCase("Test", "Test")] + [TestCase("{first_item}-{last_item}/{all_items}", "1-3/3")] + public void TablePagerInfoTextTest2(string infoFormat, string expectedInfoText) + { + // create the component + var tableComponent = Context.RenderComponent(parameters => parameters + .Add(p => p.InfoFormat, infoFormat)); + + // assert correct info-text + tableComponent.Find("div.mud-table-page-number-information").Text().Should().Be(expectedInfoText); + } + /// /// Tests the aria-labels for the pager control buttons /// @@ -2253,7 +2270,7 @@ public void TablePagerInfoTextTest() [Test] public void TablePagerControlButtonAriaLabelTest(Page controlButton, string expectedButtonAriaLabel) { - var tableComponent = Context.RenderComponent(); + var tableComponent = Context.RenderComponent(); //get control button var buttons = tableComponent.FindAll("div.mud-table-pagination-actions button"); diff --git a/src/MudBlazor.UnitTests/Components/TabsTests.cs b/src/MudBlazor.UnitTests/Components/TabsTests.cs index b2f6cc6a31fa..393ea81ada53 100644 --- a/src/MudBlazor.UnitTests/Components/TabsTests.cs +++ b/src/MudBlazor.UnitTests/Components/TabsTests.cs @@ -195,7 +195,7 @@ public void ScrollToItem_NoScrollingNeeded() styleAttr.Should().Be("transform:translateX(-0px);"); - GetSliderValue(comp).Should().BeApproximately(i * (1.0 / 6.0) * 100, 0.00001); + GetSliderValue(comp).Should().BeApproximately(i * (1.0 / 6.0) * 100, 0.01); } } @@ -256,7 +256,7 @@ public void ScrollToItem_CentralizeViewAroundActiveItem(double totalSize, double var styleAttr = toolbarWrapper.GetAttribute("style"); styleAttr.Should().Be($"transform:translateX(-{expectedTranslation.ToString(CultureInfo.InvariantCulture)}px);"); - GetSliderValue(comp).Should().BeApproximately((2.0 / 6.0) * 100.0, 0.00001); + GetSliderValue(comp).Should().BeApproximately((2.0 / 6.0) * 100.0, 0.01); } [Test] @@ -290,7 +290,7 @@ public void ScrollToItem_CentralizeViewAroundActiveItem_ScrollVertically(double var styleAttr = toolbarWrapper.GetAttribute("style"); styleAttr.Should().Be($"transform:translateY(-{expectedTranslation.ToString(CultureInfo.InvariantCulture)}px);"); - GetSliderValue(comp, "top").Should().BeApproximately((2.0 / 6.0) * 100.0, 0.00001); + GetSliderValue(comp, "top").Should().BeApproximately((2.0 / 6.0) * 100.0, 0.01); } [Test] @@ -330,7 +330,7 @@ public void ScrollToItem_CentralizeView_ActivateAllItems() var styleAttr = toolbarWrapper.GetAttribute("style"); styleAttr.Should().Be($"transform:translateX(-{expectedTranslations[i].ToString(CultureInfo.InvariantCulture)}px);"); - GetSliderValue(comp).Should().BeApproximately((i / 6.0) * 100.0, 0.00001); + GetSliderValue(comp).Should().BeApproximately((i / 6.0) * 100.0, 0.01); } } @@ -470,7 +470,7 @@ public void ScrollPrev() var styleAttr = toolbarWrapper.GetAttribute("style"); styleAttr.Should().Be($"transform:translateX(-{expectedTranslation.ToString(CultureInfo.InvariantCulture)}px);"); - GetSliderValue(comp).Should().BeApproximately((5.0 / 6.0) * 100.0, 0.00001); + GetSliderValue(comp).Should().BeApproximately((5.0 / 6.0) * 100.0, 0.01); } } @@ -493,12 +493,12 @@ public void Handle_ResizeOfPanel() var scrollButtons = comp.FindComponents(); scrollButtons.First().Instance.Disabled.Should().BeTrue(); - GetSliderValue(comp).Should().BeApproximately((1.0 / 6.0) * 100.0, 0.00001); + GetSliderValue(comp).Should().BeApproximately((1.0 / 6.0) * 100.0, 0.01); observer.UpdateTotalPanelSize(200.0); scrollButtons.First().Instance.Disabled.Should().BeTrue(); - GetSliderValue(comp).Should().BeApproximately((1.0 / 6.0) * 100.0, 0.00001); + GetSliderValue(comp).Should().BeApproximately((1.0 / 6.0) * 100.0, 0.01); } [Test] @@ -677,12 +677,12 @@ public void Handle_ResizeOfElement() var scrollButtons = comp.FindComponents(); scrollButtons.First().Instance.Disabled.Should().BeTrue(); - GetSliderValue(comp).Should().BeApproximately((1.0 / 6.0) * 100.0, 0.00001); + GetSliderValue(comp).Should().BeApproximately((1.0 / 6.0) * 100.0, 0.01); observer.UpdatePanelSize(0, 200.0); scrollButtons.First().Instance.Disabled.Should().BeTrue(); - GetSliderValue(comp).Should().BeApproximately((2.0 / 7.0) * 100.0, 0.00001); + GetSliderValue(comp).Should().BeApproximately((2.0 / 7.0) * 100.0, 0.01); } [Test] @@ -701,11 +701,11 @@ public async Task Handle_Add() comp.Instance.SetPanelActive(4); - GetSliderValue(comp).Should().BeApproximately((4.0 / 6.0) * 100.0, 0.00001); + GetSliderValue(comp).Should().BeApproximately((4.0 / 6.0) * 100.0, 0.01); await comp.Instance.AddPanel(); - GetSliderValue(comp).Should().BeApproximately((4.0 / 7.0) * 100.0, 0.00001); + GetSliderValue(comp).Should().BeApproximately((4.0 / 7.0) * 100.0, 0.01); var scrollButtons = comp.FindComponents(); scrollButtons.Should().HaveCount(2); @@ -739,7 +739,7 @@ public async Task Handle_Remove_BeforeSelection() comp.Instance.SetPanelActive(2); - GetSliderValue(comp).Should().BeApproximately((2.0 / 6.0) * 100.0, 0.00001); + GetSliderValue(comp).Should().BeApproximately((2.0 / 6.0) * 100.0, 0.01); var scrollButtons = comp.FindComponents(); @@ -784,7 +784,7 @@ public async Task Handle_Remove_AfterSelection() toolbarWrapper.HasAttribute("style").Should().Be(true); var styleAttr = toolbarWrapper.GetAttribute("style"); styleAttr.Should().Be($"transform:translateX(-100px);"); - GetSliderValue(comp).Should().BeApproximately((2.0 / 6.0) * 100.0, 0.00001); + GetSliderValue(comp).Should().BeApproximately((2.0 / 6.0) * 100.0, 0.01); } await comp.Instance.RemovePanel(5); @@ -1336,6 +1336,32 @@ public void TabPanel_Hidden_Class(bool visible) } } + [Test] + public void TabsDragAndDrop() + { + var comp = Context.RenderComponent(); + var tabs = comp.FindComponent().Instance; + + tabs.Should().NotBeNull(); + + var tab = tabs._panels[0]; + tab.Should().NotBeNull(); + var tabText = tab.Text; + + // should be 3 draggable tabs + var droptabs = comp.FindAll("div[draggable='false']"); + droptabs.Count.Should().Be(2); // disabled droptab plus beginning ghost tab + droptabs = comp.FindAll("div[draggable='true']"); + droptabs.Count.Should().Be(3); // enabled droptabs + // should be 1 draggable "drop zone" to allow reordering + var dropzone = comp.FindAll("div.mud-drop-zone"); + dropzone.Count.Should().Be(1); + // simulate dragging a tab? moving tab at index 0 to index 2 + var dropInfo = new MudItemDropInfo(tab, "mud-drop-zone", 2); + tabs.ItemUpdated(dropInfo); + comp.WaitForAssertion(() => tabs._panels[2].Text.Should().Be(tabText)); + } + [Test] public void LabelSorting_NaturalOrderIfSortingUnspecified() { diff --git a/src/MudBlazor.UnitTests/Components/TimePickerTests.cs b/src/MudBlazor.UnitTests/Components/TimePickerTests.cs index ec709ed979bf..0b4228d75c59 100644 --- a/src/MudBlazor.UnitTests/Components/TimePickerTests.cs +++ b/src/MudBlazor.UnitTests/Components/TimePickerTests.cs @@ -37,11 +37,11 @@ public IRenderedComponent OpenPicker(ComponentParameter[] } [Test] - public void TimePickerOpenButtonAriaLabel() + public void TimePickerOpenButtonDefaultAriaLabel() { var comp = Context.RenderComponent(); var openButton = comp.Find(".mud-input-adornment button"); - openButton.Attributes.GetNamedItem("aria-label")?.Value.Should().Be("Open Time Picker"); + openButton.Attributes.GetNamedItem("aria-label")?.Value.Should().Be("Open"); } [Test] diff --git a/src/MudBlazor.UnitTests/Components/UserAttributes/UserAttributesTests.cs b/src/MudBlazor.UnitTests/Components/UserAttributes/UserAttributesTests.cs index 5b3c8d35a755..57934be0cb54 100644 --- a/src/MudBlazor.UnitTests/Components/UserAttributes/UserAttributesTests.cs +++ b/src/MudBlazor.UnitTests/Components/UserAttributes/UserAttributesTests.cs @@ -50,7 +50,7 @@ public void AllMudComponents_ShouldForwardUserAttributes() { nameof(MudPopover), nameof(MudStep), nameof(MudContextualActionBar), nameof(MudHeatMapCell), "Column`1", "FooterCell`1", "HeaderCell`1", "FilterHeaderCell`1", "SelectColumn`1", - "HierarchyColumn`1", "PropertyColumn`2", "TemplateColumn`1", + "HierarchyColumn`1", "PropertyColumn`2", "TemplateColumn`1", "MudToggleItem`1", }; foreach (var componentType in mudComponentTypes) diff --git a/src/MudBlazor.UnitTests/Extensions/WebUnitsExtensionsTests.cs b/src/MudBlazor.UnitTests/Extensions/WebUnitsExtensionsTests.cs index ae7a771b2236..418780113fd9 100644 --- a/src/MudBlazor.UnitTests/Extensions/WebUnitsExtensionsTests.cs +++ b/src/MudBlazor.UnitTests/Extensions/WebUnitsExtensionsTests.cs @@ -32,5 +32,27 @@ public void All_ToPxMethods_Work() ((long?)3L).ToPx().Should().Be("3px"); ((long?)null).ToPx().Should().Be(string.Empty); } + + [Test] + public void All_ToPercentMethods_Work() + { + 0.0.ToPercent().Should().Be("0%"); + 3.3333.ToPercent().Should().Be("3.33%"); + (-3.3333).ToPercent().Should().Be("-3.33%"); + ((double?)3.3333).ToPercent().Should().Be("3.33%"); + ((double?)null).ToPercent().Should().Be(string.Empty); + + 0.ToPercent().Should().Be("0%"); + 3.ToPercent().Should().Be("3%"); + (-3).ToPercent().Should().Be("-3%"); + ((int?)3).ToPercent().Should().Be("3%"); + ((int?)null).ToPercent().Should().Be(string.Empty); + + 0L.ToPercent().Should().Be("0%"); + 3L.ToPercent().Should().Be("3%"); + (-3L).ToPercent().Should().Be("-3%"); + ((long?)3L).ToPercent().Should().Be("3%"); + ((long?)null).ToPercent().Should().Be(string.Empty); + } } } diff --git a/src/MudBlazor/Base/MudBaseInput.cs b/src/MudBlazor/Base/MudBaseInput.cs index c37118d9de41..e5f78d1f75e7 100644 --- a/src/MudBlazor/Base/MudBaseInput.cs +++ b/src/MudBlazor/Base/MudBaseInput.cs @@ -1,6 +1,7 @@ using System.Globalization; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; +using Microsoft.JSInterop; using MudBlazor.State; using MudBlazor.Utilities; @@ -41,6 +42,9 @@ protected MudBaseInput() .WithChangeHandler(UpdateInputIdStateAsync); } + [Inject] + private IJSRuntime JsRuntime { get; set; } = null!; + /// /// Allows the component to receive input. /// @@ -198,7 +202,7 @@ protected MudBaseInput() public Size IconSize { get; set; } = Size.Medium; /// - /// Occurs when the adornment text or icon has been clicked. + /// Occurs when the adornment icon (but not the text) has been clicked. /// [Parameter] public EventCallback OnAdornmentClick { get; set; } @@ -784,5 +788,13 @@ private async Task UpdateInputIdStateAsync() await _inputIdState.SetValueAsync(_componentId); } + + protected async Task HandleContainerClick() + { + if (!_isFocused && IsJSRuntimeAvailable) + { + await JsRuntime.InvokeVoidAsync("mudInput.focusInput", InputElementId); + } + } } } diff --git a/src/MudBlazor/Base/MudBooleanInput.cs b/src/MudBlazor/Base/MudBooleanInput.cs index b7adc394ce77..edb18b74675c 100644 --- a/src/MudBlazor/Base/MudBooleanInput.cs +++ b/src/MudBlazor/Base/MudBooleanInput.cs @@ -92,7 +92,7 @@ public T? Value public string? Label { get; set; } /// - /// Gets or sets whether to show a ripple effect when the user clicks the button. Default is true. + /// Shows a ripple effect when button is clicked. Default is true. /// [Parameter] [Category(CategoryTypes.FormComponent.Appearance)] diff --git a/src/MudBlazor/Base/MudFormComponent.cs b/src/MudBlazor/Base/MudFormComponent.cs index d95a582f5646..b22263e68038 100644 --- a/src/MudBlazor/Base/MudFormComponent.cs +++ b/src/MudBlazor/Base/MudFormComponent.cs @@ -650,7 +650,7 @@ protected virtual async Task ResetValueAsync() /// /// When called, the , , and properties are all reset. /// - public void ResetValidation() + public virtual void ResetValidation() { Error = false; ValidationErrors.Clear(); diff --git a/src/MudBlazor/Components/Alert/MudAlert.razor.cs b/src/MudBlazor/Components/Alert/MudAlert.razor.cs index ffcd59363dff..26f573f0bb5a 100644 --- a/src/MudBlazor/Components/Alert/MudAlert.razor.cs +++ b/src/MudBlazor/Components/Alert/MudAlert.razor.cs @@ -50,7 +50,7 @@ private HorizontalAlignment ConvertHorizontalAlignment(HorizontalAlignment conte public bool RightToLeft { get; set; } /// - /// Gets or sets the position of the text to the start (Left in LTR and right in RTL). + /// Position of the text to the start (Left in LTR and right in RTL). /// /// /// Defaults to . @@ -66,7 +66,7 @@ private HorizontalAlignment ConvertHorizontalAlignment(HorizontalAlignment conte public EventCallback CloseIconClicked { get; set; } /// - /// Gets or sets the icon used for the close button. + /// Icon used for the close button. /// /// /// Defaults to . This icon is only displayed when the property is true. @@ -76,7 +76,7 @@ private HorizontalAlignment ConvertHorizontalAlignment(HorizontalAlignment conte public string CloseIcon { get; set; } = Icons.Material.Filled.Close; /// - /// Gets or sets whether a close icon is displayed. + /// Displays a close icon. /// /// /// To customize which icon is displayed for the close icon, set the property. @@ -86,7 +86,7 @@ private HorizontalAlignment ConvertHorizontalAlignment(HorizontalAlignment conte public bool ShowCloseIcon { get; set; } /// - /// Gets or sets the size of the drop shadow. + /// Size of the drop shadow. /// /// /// Defaults to 0. A higher number creates a heavier drop shadow. Use a value of 0 for no shadow. @@ -96,18 +96,18 @@ private HorizontalAlignment ConvertHorizontalAlignment(HorizontalAlignment conte public int Elevation { set; get; } = 0; /// - /// Gets or sets whether rounded corners are disabled. + /// Disables rounded corners. /// /// /// Defaults to false. - /// Can be overridden by + /// Override with . /// [Parameter] [Category(CategoryTypes.Alert.Appearance)] public bool Square { get; set; } = MudGlobal.Rounded == false; /// - /// Gets or sets whether compact padding will be used. + /// Uses compact padding. /// /// /// Defaults to false. @@ -117,7 +117,7 @@ private HorizontalAlignment ConvertHorizontalAlignment(HorizontalAlignment conte public bool Dense { get; set; } /// - /// Gets or sets whether no icon is displayed. + /// Displays the alert without an icon. /// /// /// Defaults to false. To customize the icon, use the property. @@ -127,7 +127,7 @@ private HorizontalAlignment ConvertHorizontalAlignment(HorizontalAlignment conte public bool NoIcon { get; set; } /// - /// Gets or sets the severity of the alert. + /// Severity of the alert. /// /// /// The severity determines the color and icon used. @@ -137,7 +137,7 @@ private HorizontalAlignment ConvertHorizontalAlignment(HorizontalAlignment conte public Severity Severity { get; set; } = Severity.Normal; /// - /// Gets or sets the display variant to use. + /// Display variant to use. /// /// /// Defaults to . The variant changes the appearance of the alert, such as Text, Outlined, or Filled. @@ -147,17 +147,17 @@ private HorizontalAlignment ConvertHorizontalAlignment(HorizontalAlignment conte public Variant Variant { get; set; } = Variant.Text; /// - /// Gets or sets the content within the alert. + /// Content within the alert. /// /// - /// This property allows for custom content to displayed inside of the alert, but it is not required. + /// This property allows for custom content to displayed inside of the alert, but is not required. /// [Parameter] [Category(CategoryTypes.Alert.Behavior)] public RenderFragment? ChildContent { get; set; } /// - /// Gets or sets the icon displayed for this alert. + /// Icon displayed for this alert. /// /// /// Defaults to null. When set, the custom icon will be displayed. Otherwise, the icon will depend on the property. diff --git a/src/MudBlazor/Components/Autocomplete/MudAutocomplete.razor.cs b/src/MudBlazor/Components/Autocomplete/MudAutocomplete.razor.cs index 5ebd0ad1a47c..79a8264be093 100644 --- a/src/MudBlazor/Components/Autocomplete/MudAutocomplete.razor.cs +++ b/src/MudBlazor/Components/Autocomplete/MudAutocomplete.razor.cs @@ -375,7 +375,7 @@ protected string GetListItemClassname(bool isSelected) => /// [Parameter] [Category(CategoryTypes.FormComponent.ListBehavior)] - public bool Modal { get; set; } = true; + public bool Modal { get; set; } = MudGlobal.PopoverDefaults.ModalOverlay; /// /// Determines the width of this Popover dropdown in relation to the parent container. @@ -739,7 +739,20 @@ public async Task OpenMenuAsync() if (MaxItems.HasValue) { - searchedItems = searchedItems.Take(MaxItems.Value).ToArray(); + // Get range of items based off selected item so the selected item can be scrolled to when strict is set to false + if (!Strict && searchedItems.Length != 0 && !EqualityComparer.Default.Equals(Value, default(T))) + { + int split = (MaxItems.Value / 2) + 1; + int valueIndex = Array.IndexOf(searchedItems, Value); + int endIndex = Math.Min(valueIndex + split, searchedItems.Length); + int startIndex = endIndex - Math.Min(MaxItems.Value, searchedItems.Length); + + searchedItems = searchedItems.Take(new Range(startIndex, endIndex)).ToArray(); + } + else + { + searchedItems = searchedItems.Take(MaxItems.Value).ToArray(); + } } _items = searchedItems; @@ -763,6 +776,12 @@ public async Task OpenMenuAsync() _opening = false; StateHasChanged(); + + // If not strict scroll to the selected item + if (!Strict && _selectedListItemIndex > 0) + { + await ScrollToListItemAsync(_selectedListItemIndex); + } } /// @@ -1182,9 +1201,10 @@ private async Task OnTextChangedAsync(string? text) private async Task ListItemOnClickAsync(T item) { - await SelectOptionAsync(item); _handleNextFocus = true; // Let the event handler know it doesn't need to do anything. await FocusAsync(); + + await SelectOptionAsync(item); } } } diff --git a/src/MudBlazor/Components/Avatar/MudAvatar.razor.cs b/src/MudBlazor/Components/Avatar/MudAvatar.razor.cs index d676d435ba66..ed7c762964ae 100644 --- a/src/MudBlazor/Components/Avatar/MudAvatar.razor.cs +++ b/src/MudBlazor/Components/Avatar/MudAvatar.razor.cs @@ -45,7 +45,7 @@ partial class MudAvatar : MudComponentBase, IDisposable /// /// /// Defaults to false. - /// Can be overridden by + /// Override with . /// [Parameter] [Category(CategoryTypes.Avatar.Appearance)] @@ -56,7 +56,7 @@ partial class MudAvatar : MudComponentBase, IDisposable /// /// /// Defaults to false. - /// Can be overridden by + /// Override with . /// When true, the border-radius style is set to the theme's default value. /// [Parameter] @@ -97,7 +97,7 @@ partial class MudAvatar : MudComponentBase, IDisposable /// The content within the avatar. /// /// - /// This property allows for custom content to displayed inside of the avatar, but it is not required. + /// This property allows for custom content to displayed inside of the avatar, but is not required. /// [Parameter] [Category(CategoryTypes.Avatar.Behavior)] diff --git a/src/MudBlazor/Components/Avatar/MudAvatarGroup.razor.cs b/src/MudBlazor/Components/Avatar/MudAvatarGroup.razor.cs index dc8fb45bbb35..3a144a412c3f 100644 --- a/src/MudBlazor/Components/Avatar/MudAvatarGroup.razor.cs +++ b/src/MudBlazor/Components/Avatar/MudAvatarGroup.razor.cs @@ -67,7 +67,7 @@ partial class MudAvatarGroup : MudComponentBase /// /// /// Defaults to false. - /// Can be overridden by + /// Override with . /// When true, the border-radius CSS style is set to 0. /// [Parameter] @@ -79,7 +79,7 @@ partial class MudAvatarGroup : MudComponentBase /// /// /// Defaults to false. - /// Can be overridden by + /// Override with . /// When true, the border-radius style is set to the theme's default value. /// [Parameter] diff --git a/src/MudBlazor/Components/ButtonGroup/MudButtonGroup.razor.cs b/src/MudBlazor/Components/ButtonGroup/MudButtonGroup.razor.cs index 57dfeb62c6db..98c721859b96 100644 --- a/src/MudBlazor/Components/ButtonGroup/MudButtonGroup.razor.cs +++ b/src/MudBlazor/Components/ButtonGroup/MudButtonGroup.razor.cs @@ -40,7 +40,7 @@ public partial class MudButtonGroup : MudComponentBase /// The custom content within this group. /// /// - /// This property allows for custom content to displayed inside of the group, but it is not required. + /// This property allows for custom content to displayed inside of the group, but is not required. /// [Parameter] [Category(CategoryTypes.ButtonGroup.Behavior)] diff --git a/src/MudBlazor/Components/Card/MudCard.razor.cs b/src/MudBlazor/Components/Card/MudCard.razor.cs index 92e862f59f9f..901629c6f492 100644 --- a/src/MudBlazor/Components/Card/MudCard.razor.cs +++ b/src/MudBlazor/Components/Card/MudCard.razor.cs @@ -32,7 +32,7 @@ public partial class MudCard : MudComponentBase /// /// /// Defaults to false. - /// Can be overridden by . + /// Override with .. /// [Parameter] [Category(CategoryTypes.Card.Appearance)] diff --git a/src/MudBlazor/Components/Chart/Models/ChartOptions.cs b/src/MudBlazor/Components/Chart/Models/ChartOptions.cs index 5efea1147a47..0d535d635b58 100644 --- a/src/MudBlazor/Components/Chart/Models/ChartOptions.cs +++ b/src/MudBlazor/Components/Chart/Models/ChartOptions.cs @@ -103,7 +103,7 @@ public class ChartOptions /// /// Enables smooth color transitions for heatmap cells and removes all padding between cells in a - /// Defaults to false + /// Defaults to false /// public bool EnableSmoothGradient { get; set; } = false; @@ -121,26 +121,25 @@ public class ChartOptions /// /// Enables tooltips for values - /// Defaults to true + /// Defaults to true /// public bool ShowToolTips { get; set; } = true; /// /// Enables labels for every box in a - /// Defaults to true + /// Defaults to true /// public bool ShowLabels { get; set; } = true; /// /// Enables label values for the legend boxes in a - /// Defaults to false + /// Defaults to false /// - /// public bool ShowLegendLabels { get; set; } = false; /// /// The format applied to labels for every box in a - /// Defaults to "F2" + /// Defaults to "F2" /// public string ValueFormatString { get; set; } = "F2"; diff --git a/src/MudBlazor/Components/ChatBubble/MudChat.razor.cs b/src/MudBlazor/Components/ChatBubble/MudChat.razor.cs index fc420ecb8d6c..a493f3a5bfd6 100644 --- a/src/MudBlazor/Components/ChatBubble/MudChat.razor.cs +++ b/src/MudBlazor/Components/ChatBubble/MudChat.razor.cs @@ -31,7 +31,7 @@ public partial class MudChat : MudComponentBase public Color Color { get; set; } = Color.Default; /// - /// Gets or sets the display variant to use. + /// Display variant to use. /// /// /// Defaults to . The variant changes the appearance of the chat bubbles, such as Text, Outlined, or Filled. @@ -63,7 +63,7 @@ public partial class MudChat : MudComponentBase public RenderFragment? ChildContent { get; set; } /// - /// Gets or sets the size of the drop shadow. + /// Size of the drop shadow. /// /// /// Defaults to 0. A higher number creates a heavier drop shadow. Use a value of 0 for no shadow. @@ -77,7 +77,7 @@ public partial class MudChat : MudComponentBase /// /// /// Defaults to false. - /// Can be overridden by . + /// Override with .. /// [Parameter] [Category(CategoryTypes.Alert.Appearance)] diff --git a/src/MudBlazor/Components/ColorPicker/MudColorPicker.razor b/src/MudBlazor/Components/ColorPicker/MudColorPicker.razor index 16e2cb08f2de..71e1c7777d68 100644 --- a/src/MudBlazor/Components/ColorPicker/MudColorPicker.razor +++ b/src/MudBlazor/Components/ColorPicker/MudColorPicker.razor @@ -103,7 +103,7 @@ {
+ aria-label="@(_collectionOpen ? @Localizer[LanguageResource.MudColorPicker_HideSwatches] : @Localizer[LanguageResource.MudColorPicker_ShowSwatches])">
} diff --git a/src/MudBlazor/Components/ColorPicker/MudColorPicker.razor.cs b/src/MudBlazor/Components/ColorPicker/MudColorPicker.razor.cs index f205f64e3c04..76d483550d80 100644 --- a/src/MudBlazor/Components/ColorPicker/MudColorPicker.razor.cs +++ b/src/MudBlazor/Components/ColorPicker/MudColorPicker.razor.cs @@ -54,7 +54,6 @@ public MudColorPicker() : base(new DefaultConverter()) ShowToolbar = false; Value = "#594ae2"; // MudBlazor Blue Text = GetColorTextValue(); - AdornmentAriaLabel = "Open Color Picker"; using var registerScope = CreateRegisterScope(); _throttleIntervalState = registerScope.RegisterParameter(nameof(ThrottleInterval)) .WithParameter(() => ThrottleInterval) @@ -307,6 +306,7 @@ protected override void OnInitialized() { base.OnInitialized(); SetThrottle(_throttleIntervalState.Value); + AdornmentAriaLabel ??= Localizer[Resources.LanguageResource.MudColorPicker_Open]; } private void OnThrottleIntervalParameterChanged(ParameterChangedEventArgs args) diff --git a/src/MudBlazor/Components/DataGrid/Column.razor.cs b/src/MudBlazor/Components/DataGrid/Column.razor.cs index 352579d0153c..5f4738a6f29f 100644 --- a/src/MudBlazor/Components/DataGrid/Column.razor.cs +++ b/src/MudBlazor/Components/DataGrid/Column.razor.cs @@ -133,7 +133,7 @@ public abstract partial class Column<[DynamicallyAccessedMembers(DynamicallyAcce public EventCallback GroupByOrderChanged { get; set; } /// - /// Whether the column is indented 48px beyond it's parent when grouped. + /// Indents the column 48px beyond its parent when grouped. /// [Parameter] public bool GroupIndented { get; set; } = true; diff --git a/src/MudBlazor/Components/DataGrid/DataGridGroupRow.razor b/src/MudBlazor/Components/DataGrid/DataGridGroupRow.razor index 8d91dabe515f..b6904e1739c7 100644 --- a/src/MudBlazor/Components/DataGrid/DataGridGroupRow.razor +++ b/src/MudBlazor/Components/DataGrid/DataGridGroupRow.razor @@ -7,13 +7,17 @@
- @if (GroupDefinition?.GroupTemplate == null) + @if (GroupDefinition.GroupTemplate is not null) { - @GroupDefinition?.Title: @(Items?.Key ?? "null") + @GroupDefinition.GroupTemplate(GroupDefinition) + } + else if (GroupDefinition.DataGrid.GroupTemplate is not null) + { + @GroupDefinition.DataGrid.GroupTemplate(GroupDefinition) } else { - @GroupDefinition.GroupTemplate(GroupDefinition) + @GroupDefinition.Title: @(Items?.Key ?? "null") }
diff --git a/src/MudBlazor/Components/DataGrid/GroupDefinition.cs b/src/MudBlazor/Components/DataGrid/GroupDefinition.cs index 31bf117ac7f2..26218427b68e 100644 --- a/src/MudBlazor/Components/DataGrid/GroupDefinition.cs +++ b/src/MudBlazor/Components/DataGrid/GroupDefinition.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; namespace MudBlazor; @@ -11,10 +12,15 @@ namespace MudBlazor; /// Represents the grouping information for columns in a . /// /// -public class GroupDefinition +public class GroupDefinition<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T> { private GroupDefinition? _innerGroup; + /// + /// The which contains this group definition. + /// + public required MudDataGrid DataGrid { get; init; } + /// /// The LINQ definition of the grouping. /// diff --git a/src/MudBlazor/Components/DataGrid/HeaderCell.razor b/src/MudBlazor/Components/DataGrid/HeaderCell.razor index 4856e826eaa9..31d87e308cb5 100644 --- a/src/MudBlazor/Components/DataGrid/HeaderCell.razor +++ b/src/MudBlazor/Components/DataGrid/HeaderCell.razor @@ -142,7 +142,7 @@ else if (Column != null && !Column.HiddenState.Value) @if (filterable && DataGrid.FilterMode == DataGridFilterMode.ColumnFilterMenu) { - + @if (Column.FilterTemplate != null) { @Column.FilterTemplate(Column.FilterContext) diff --git a/src/MudBlazor/Components/DataGrid/HeaderCell.razor.cs b/src/MudBlazor/Components/DataGrid/HeaderCell.razor.cs index c54a36e82e1f..f6a79585edfc 100644 --- a/src/MudBlazor/Components/DataGrid/HeaderCell.razor.cs +++ b/src/MudBlazor/Components/DataGrid/HeaderCell.razor.cs @@ -31,7 +31,7 @@ public partial class HeaderCell<[DynamicallyAccessedMembers(DynamicallyAccessedM public MudDataGrid DataGrid { get; set; } /// - /// Whether the display should be right to left + /// Displays the content right-to-left. /// [CascadingParameter(Name = "RightToLeft")] public bool RightToLeft { get; set; } @@ -401,7 +401,7 @@ internal async Task RemoveSortAsync() DataGrid.DropContainerHasChanged(); } - internal void AddFilter() + internal void AddFilter(MouseEventArgs args = null) { var filterDefinition = Column?.FilterContext.FilterDefinition; if (DataGrid.FilterMode == DataGridFilterMode.Simple && filterDefinition != null) @@ -410,6 +410,11 @@ internal void AddFilter() { DataGrid.FilterDefinitions.Add(filterDefinition.Clone()); } + if (args != null) + { + DataGrid._openPosition.Top = args.PageY; + DataGrid._openPosition.Left = args.PageX; + } DataGrid.OpenFilters(); } else if (DataGrid.FilterMode == DataGridFilterMode.ColumnFilterMenu) @@ -419,12 +424,24 @@ internal void AddFilter() } } - internal void OpenFilters() + internal void OpenFilters(MouseEventArgs args = null) { if (DataGrid.FilterMode == DataGridFilterMode.Simple) + { + if (args != null) + { + DataGrid._openPosition.Top = args.PageY; + DataGrid._openPosition.Left = args.PageX; + } DataGrid.OpenFilters(); + } else if (DataGrid.FilterMode == DataGridFilterMode.ColumnFilterMenu) { + if (args != null) + { + DataGrid._openPosition.Top = args.PageY; + DataGrid._openPosition.Left = args.PageX; + } _filtersMenuVisible = true; DataGrid.DropContainerHasChanged(); } diff --git a/src/MudBlazor/Components/DataGrid/HierarchyColumn.razor.cs b/src/MudBlazor/Components/DataGrid/HierarchyColumn.razor.cs index 6d1294b10366..a12329cb41dc 100644 --- a/src/MudBlazor/Components/DataGrid/HierarchyColumn.razor.cs +++ b/src/MudBlazor/Components/DataGrid/HierarchyColumn.razor.cs @@ -19,7 +19,7 @@ public partial class HierarchyColumn<[DynamicallyAccessedMembers(DynamicallyAcce private readonly HashSet> _initiallyExpandedItems = []; /// - /// Whether the display should be right to left + /// Displays the content right-to-left. /// [CascadingParameter(Name = "RightToLeft")] public bool RightToLeft { get; set; } diff --git a/src/MudBlazor/Components/DataGrid/MudDataGrid.razor b/src/MudBlazor/Components/DataGrid/MudDataGrid.razor index 67c191054fab..58678aaa714c 100644 --- a/src/MudBlazor/Components/DataGrid/MudDataGrid.razor +++ b/src/MudBlazor/Components/DataGrid/MudDataGrid.razor @@ -62,7 +62,7 @@ - @if (FilterTemplate == null) { @@ -92,7 +92,7 @@ - @@ -154,11 +154,11 @@ @if (context.HeaderCell.hasFilter) { - + } else { - + } @if (ColumnsPanelReordering) @@ -321,7 +321,7 @@ @ @Localizer[LanguageResource.MudDataGrid_Columns] - @if (dataGrid.Groupable) + @if (dataGrid.IsGrouped) { @Localizer[LanguageResource.MudDataGrid_ExpandAllGroups] @Localizer[LanguageResource.MudDataGrid_CollapseAllGroups] diff --git a/src/MudBlazor/Components/DataGrid/MudDataGrid.razor.cs b/src/MudBlazor/Components/DataGrid/MudDataGrid.razor.cs index 68524a64b5a9..2ccad9b67d51 100644 --- a/src/MudBlazor/Components/DataGrid/MudDataGrid.razor.cs +++ b/src/MudBlazor/Components/DataGrid/MudDataGrid.razor.cs @@ -42,11 +42,21 @@ public partial class MudDataGrid<[DynamicallyAccessedMembers(DynamicallyAccessed internal Dictionary _groupExpansionsDict = []; private GridData _serverData = new() { TotalItems = 0, Items = Array.Empty() }; private Func> _defaultFilterDefinitionFactory = () => new FilterDefinition(); + internal (double Top, double Left) _openPosition = (0, 0); private readonly ParameterState _selectedItemState; private readonly ParameterState> _selectedItemsState; private readonly ParameterState _expandSingleRowState; + /// + /// Inline data attributes for positioning the menu at the cursor's location. + /// + internal Dictionary PositionAttributes => new() + { + { "data-pc-x", _openPosition.Left.ToString(CultureInfo.InvariantCulture) }, + { "data-pc-y", _openPosition.Top.ToString(CultureInfo.InvariantCulture) } + }; + public MudDataGrid() { Selection = new HashSet(Comparer); @@ -451,7 +461,7 @@ private Task ItemUpdatedAsync(MudItemDropInfo> dropItem) /// /// /// Defaults to false. - /// Can be overridden by + /// Override with . /// [Parameter] public bool Square { get; set; } = MudGlobal.Rounded == false; @@ -1002,9 +1012,7 @@ public bool Groupable if (!_groupable) { _groupDefinition = null; - - foreach (var column in RenderedColumns) - column.RemoveGrouping().CatchAndLog(); + // do not need to RemoveGrouping here, if Groupable is set to false they won't show } } } @@ -1082,6 +1090,15 @@ public bool Groupable [Parameter] public IEqualityComparer Comparer { get; set; } = EqualityComparer.Default; +#nullable enable + /// + /// The default template used to display column grouping for any column that is grouped. + /// + /// Can be overridden by using the column level GroupTemplate, defaults to null. + [Parameter] + public RenderFragment>? GroupTemplate { get; set; } +#nullable disable + #endregion #region Properties @@ -1185,13 +1202,13 @@ public IEnumerable FilteredItems public Interfaces.IForm Validator { get; set; } = new DataGridRowValidator(); /// - /// Returns true if is true and at least one column has Grouping toggled on. + /// Returns true if the grid successfully grouped any column /// public bool IsGrouped { get { - return Groupable && RenderedColumns.FirstOrDefault(x => x.GroupingState.Value) != null; + return _groupDefinition != null; } } @@ -1968,8 +1985,13 @@ internal async Task ShowAllColumnsAsync() /// /// Shows a panel that lets you show, hide, filter, groupedColumns, sort and re-arrange columns. /// - public void ShowColumnsPanel() + public void ShowColumnsPanel(MouseEventArgs args = null) { + if (args != null) + { + _openPosition.Top = args.PageY; + _openPosition.Left = args.PageX; + } _columnsPanelVisible = true; StateHasChanged(); } @@ -2034,16 +2056,22 @@ public void GroupItems(bool noStateChange = false) _groupDefinition = default; - if (!IsGrouped || GetFilteredItemsCount() == 0) + // get all columns that have Groupable set to true + var groupedColumns = RenderedColumns.Where(x => x.groupable).ToList(); + // is groupable on either DataGrid level or column level + var isGroupable = Groupable || groupedColumns.Count > 0; + // any columns that are groupable and have grouping set to true + groupedColumns = [.. groupedColumns.Where(x => x.GroupingState.Value).OrderBy(x => x._groupByOrderState.Value)]; + // it's only groupable if a column can be grouped + isGroupable = isGroupable && groupedColumns.Count > 0; + + if (!isGroupable || GetFilteredItemsCount() == 0) { if (_isFirstRendered && !noStateChange) StateHasChanged(); return; } - // get all columns that are grouped in the order they are grouped - var groupedColumns = RenderedColumns.Where(x => x.GroupingState.Value).OrderBy(x => x._groupByOrderState.Value).ToList(); - // Initialize with the first group definition _groupDefinition = ProcessGroup(groupedColumns[0]); @@ -2094,6 +2122,7 @@ private GroupDefinition ProcessGroup(Column column) column._groupExpandedState.Value; return new() { + DataGrid = this, Selector = column.groupBy, Expanded = expanded, GroupTemplate = column.GroupTemplate, @@ -2118,6 +2147,7 @@ internal IEnumerable> GetGroupDefinitions(GroupDefinition } result.Add(new GroupDefinition { + DataGrid = this, Selector = groupDef.Selector, Expanded = expanded, GroupTemplate = groupDef.GroupTemplate, diff --git a/src/MudBlazor/Components/DatePicker/MudBaseDatePicker.razor.cs b/src/MudBlazor/Components/DatePicker/MudBaseDatePicker.razor.cs index f454afbe38f6..d7ad2360375c 100644 --- a/src/MudBlazor/Components/DatePicker/MudBaseDatePicker.razor.cs +++ b/src/MudBlazor/Components/DatePicker/MudBaseDatePicker.razor.cs @@ -22,7 +22,6 @@ protected MudBaseDatePicker() : base(new DefaultConverter Culture = CultureInfo.CurrentCulture }) { - AdornmentAriaLabel = "Open Date Picker"; _mudPickerCalendarContentElementId = Identifier.Create(); } @@ -707,6 +706,7 @@ private Typo GetMonthTypo(DateTime month) protected override void OnInitialized() { base.OnInitialized(); + AdornmentAriaLabel ??= Localizer[Resources.LanguageResource.MudBaseDatePicker_Open]; CurrentView = OpenTo; if (HighlightedDate is not null) return; diff --git a/src/MudBlazor/Components/DatePicker/MudDateRangePicker.razor.cs b/src/MudBlazor/Components/DatePicker/MudDateRangePicker.razor.cs index 099fe5bffcdb..6ced09b0ab52 100644 --- a/src/MudBlazor/Components/DatePicker/MudDateRangePicker.razor.cs +++ b/src/MudBlazor/Components/DatePicker/MudDateRangePicker.razor.cs @@ -29,7 +29,6 @@ public MudDateRangePicker() .WithChangeHandler(RecalculateValidDays); DisplayMonths = 2; - AdornmentAriaLabel = "Open Date Range Picker"; } /// diff --git a/src/MudBlazor/Components/Drawer/MudDrawer.razor.cs b/src/MudBlazor/Components/Drawer/MudDrawer.razor.cs index 8f66655db95d..9eed1ceae669 100644 --- a/src/MudBlazor/Components/Drawer/MudDrawer.razor.cs +++ b/src/MudBlazor/Components/Drawer/MudDrawer.razor.cs @@ -162,7 +162,7 @@ public MudDrawer() public bool Overlay { get; set; } = true; /// - /// Sets a value indicating whether the overlay should automatically close when clicked. + /// Automatically closes the drawer when clicking on the overlay. /// /// /// If the is set to , an overlay will be displayed. @@ -288,7 +288,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) await UpdateHeightAsync(); if (!_disposed) { - await BrowserViewportService.SubscribeAsync(this, fireImmediately: true); + _ = BrowserViewportService.SubscribeAsync(this, fireImmediately: true); } _isRendered = true; diff --git a/src/MudBlazor/Components/DropZone/MudDropContainer.razor.cs b/src/MudBlazor/Components/DropZone/MudDropContainer.razor.cs index 67c368f33b8a..1b66cdfc3d19 100644 --- a/src/MudBlazor/Components/DropZone/MudDropContainer.razor.cs +++ b/src/MudBlazor/Components/DropZone/MudDropContainer.razor.cs @@ -55,8 +55,9 @@ public partial class MudDropContainer : MudComponentBase where T : notnull public RenderFragment? ItemRenderer { get; set; } /// - /// The function which determines whether an item can be dropped within a drop zone. + /// The function which determines whether an item is within a . /// + /// Can be overridden by child 's with their owm implementation of [Parameter] [Category(CategoryTypes.DropZone.Items)] public Func? ItemsSelector { get; set; } diff --git a/src/MudBlazor/Components/DropZone/MudDropZone.razor.cs b/src/MudBlazor/Components/DropZone/MudDropZone.razor.cs index fee5a3927ee3..9c6ec9303fc2 100644 --- a/src/MudBlazor/Components/DropZone/MudDropZone.razor.cs +++ b/src/MudBlazor/Components/DropZone/MudDropZone.razor.cs @@ -63,7 +63,7 @@ public partial class MudDropZone : MudComponentBase, IDisposable where T : no public RenderFragment? ItemRenderer { get; set; } /// - /// The function which determines whether an item can be dropped within this drop zone. + /// The function which determines whether an item is within this . /// /// /// When set, overrides the function. diff --git a/src/MudBlazor/Components/ExpansionPanel/MudExpansionPanel.razor.cs b/src/MudBlazor/Components/ExpansionPanel/MudExpansionPanel.razor.cs index 47ad7f5e69df..6e2cf55e221d 100644 --- a/src/MudBlazor/Components/ExpansionPanel/MudExpansionPanel.razor.cs +++ b/src/MudBlazor/Components/ExpansionPanel/MudExpansionPanel.razor.cs @@ -155,7 +155,7 @@ public partial class MudExpansionPanel : MudComponentBase, IDisposable public RenderFragment? ChildContent { get; set; } /// - /// Indicates whether the next panel is currently expanded. + /// The next panel is currently expanded. /// public bool NextPanelExpanded { get; set; } diff --git a/src/MudBlazor/Components/ExpansionPanel/MudExpansionPanels.razor.cs b/src/MudBlazor/Components/ExpansionPanel/MudExpansionPanels.razor.cs index c4e7eaed0728..419116267e0d 100644 --- a/src/MudBlazor/Components/ExpansionPanel/MudExpansionPanels.razor.cs +++ b/src/MudBlazor/Components/ExpansionPanel/MudExpansionPanels.razor.cs @@ -24,7 +24,7 @@ public partial class MudExpansionPanels : MudComponentBase /// /// /// Defaults to false. - /// Can be overridden by . + /// Override with .. /// [Parameter] [Category(CategoryTypes.ExpansionPanel.Appearance)] diff --git a/src/MudBlazor/Components/Field/MudField.razor.cs b/src/MudBlazor/Components/Field/MudField.razor.cs index 75380ecf18d2..e4d45e0789bf 100644 --- a/src/MudBlazor/Components/Field/MudField.razor.cs +++ b/src/MudBlazor/Components/Field/MudField.razor.cs @@ -188,7 +188,7 @@ public partial class MudField : MudComponentBase public Size IconSize { get; set; } = Size.Medium; /// - /// Occurs when the adornment text or icon has been clicked. + /// Occurs when the adornment icon (but not the text) has been clicked. /// [Parameter] public EventCallback OnAdornmentClick { get; set; } diff --git a/src/MudBlazor/Components/FileUpload/MudFileUpload.razor.cs b/src/MudBlazor/Components/FileUpload/MudFileUpload.razor.cs index b170b2873002..50f283454de9 100644 --- a/src/MudBlazor/Components/FileUpload/MudFileUpload.razor.cs +++ b/src/MudBlazor/Components/FileUpload/MudFileUpload.razor.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging; using Microsoft.JSInterop; using MudBlazor.Interfaces; +using MudBlazor.Resources; using MudBlazor.State; using MudBlazor.Utilities; @@ -26,6 +27,9 @@ public partial class MudFileUpload : MudFormComponent, IActivatabl [Inject] private IJSRuntime JsRuntime { get; set; } = null!; + [Inject] + private InternalMudLocalizer Localizer { get; set; } = null!; + /// /// Creates a new instance. /// @@ -38,6 +42,7 @@ public MudFileUpload() : base(new DefaultConverter()) } private readonly string _id = Identifier.Create(); + private readonly List _validationErrors = []; protected string Classname => new CssBuilder("mud-file-upload") @@ -153,6 +158,16 @@ public MudFileUpload() : base(new DefaultConverter()) [Category(CategoryTypes.FileUpload.Behavior)] public int MaximumFileCount { get; set; } = 10; + /// + /// The maximum file size in bytes. + /// + /// + /// Defaults to null (no limit). When a file exceeds this limit, the upload for that file will be prevented. + /// + [Parameter] + [Category(CategoryTypes.FileUpload.Behavior)] + public long? MaxFileSize { get; set; } + /// /// Prevents the user from uploading files. /// @@ -180,7 +195,11 @@ public MudFileUpload() : base(new DefaultConverter()) public async Task ClearAsync() { + ValidationErrors.RemoveAll(_validationErrors.Contains); + + _validationErrors.Clear(); _numberOfActiveFileInputs = 1; + await NotifyValueChangedAsync(default); await JsRuntime.InvokeVoidAsyncWithErrorHandling("mudInput.resetValue", GetActiveInputId()); } @@ -204,27 +223,22 @@ private async Task OnChangeAsync(InputFileChangeEventArgs args) _numberOfActiveFileInputs++; if (GetDisabledState()) - { return; - } + await ProcessFileChangeAsync(args); + } + + private async Task ProcessFileChangeAsync(InputFileChangeEventArgs args) + { T? value; + if (typeof(T) == typeof(IReadOnlyList)) { - var newFiles = args.GetMultipleFiles(MaximumFileCount); - if (AppendMultipleFiles && _filesState.Value is IReadOnlyList oldFiles) - { - var allFiles = oldFiles.Concat(newFiles).ToList(); - value = (T)(object)allFiles.AsReadOnly(); - } - else - { - value = (T)newFiles; - } + value = (T?)(object)ProcessMultipleFiles(args.GetMultipleFiles(MaximumFileCount)); } else if (typeof(T) == typeof(IBrowserFile)) { - value = args.FileCount == 1 ? (T)args.File : default; + value = (T?)ProcessSingleFile(args.FileCount == 1 ? args.File : null); } else { @@ -233,10 +247,46 @@ private async Task OnChangeAsync(InputFileChangeEventArgs args) await NotifyValueChangedAsync(value); - if (!Error || !SuppressOnChangeWhenInvalid) // only trigger FilesChanged if validation passes or SuppressOnChangeWhenInvalid is false - { + if (!Error || !SuppressOnChangeWhenInvalid) await OnFilesChanged.InvokeAsync(args); + } + + private IReadOnlyList ProcessMultipleFiles(IReadOnlyCollection files) + { + var validFiles = new List(); + + foreach (var file in files) + { + if (MaxFileSize.HasValue && file.Size > MaxFileSize.Value) + { + _validationErrors.Add(Localizer[LanguageResource.MudFileUpload_FileSizeError, file.Name, MaxFileSize.Value.ToString()]); + } + else + { + validFiles.Add(file); + } } + + var newFiles = validFiles.AsReadOnly(); + + if (AppendMultipleFiles && _filesState.Value is IReadOnlyList oldFiles) + return oldFiles.Concat(newFiles).ToList().AsReadOnly(); + + return newFiles; + } + + private IBrowserFile? ProcessSingleFile(IBrowserFile? file) + { + if (file == null) + return null; + + if (MaxFileSize.HasValue && file.Size > MaxFileSize.Value) + { + _validationErrors.Add(Localizer[LanguageResource.MudFileUpload_FileSizeError, file.Name, MaxFileSize.Value.ToString()]); + return null; + } + + return file; } protected override void OnInitialized() @@ -260,5 +310,21 @@ private async Task NotifyValueChangedAsync(T? value) protected override T? ReadValue() => _filesState.Value; protected override Task WriteValueAsync(T? value) => _filesState.SetValueAsync(value); + + protected override async Task ValidateValue() + { + await base.ValidateValue(); + + ValidationErrors = [.. ValidationErrors, .. _validationErrors]; + Error = ValidationErrors.Count > 0; + ErrorText = ValidationErrors.FirstOrDefault(); + } + + public override void ResetValidation() + { + _validationErrors.Clear(); + + base.ResetValidation(); + } } } diff --git a/src/MudBlazor/Components/Input/MudInput.razor.cs b/src/MudBlazor/Components/Input/MudInput.razor.cs index 6ef71108ac73..0d93ee46af5e 100644 --- a/src/MudBlazor/Components/Input/MudInput.razor.cs +++ b/src/MudBlazor/Components/Input/MudInput.razor.cs @@ -376,7 +376,7 @@ protected override async ValueTask DisposeAsyncCore() { if (IsJSRuntimeAvailable) { - await ElementReference.MudDetachBlurEventWithJS(_dotNetReferenceLazy.Value); + await JsRuntime.InvokeVoidAsyncWithErrorHandling("mudElementRef.removeOnBlurEvent", ElementReference); if (AutoGrow) { await JsRuntime.InvokeVoidAsyncWithErrorHandling("mudInputAutoGrow.destroy", ElementReference); diff --git a/src/MudBlazor/Components/Menu/MudMenu.razor b/src/MudBlazor/Components/Menu/MudMenu.razor index f8d45439581a..e380b2e6acbf 100644 --- a/src/MudBlazor/Components/Menu/MudMenu.razor +++ b/src/MudBlazor/Components/Menu/MudMenu.razor @@ -71,7 +71,7 @@ @* The portal has to include the cascading values inside because it's not able to teletransport the cascade *@ (nameof(Open)) .WithParameter(() => Open) @@ -73,15 +71,23 @@ public MudMenu() .AddClass("mud-disabled", Disabled) .Build(); - /// - /// Inline styles for positioning the menu at the cursor's location. - /// + [ExcludeFromCodeCoverage] + [Obsolete($"Will be removed in future, replaced by {nameof(PositionAttributes)}.")] protected string Stylename => new StyleBuilder() .AddStyle("top", _openPosition.Top.ToPx(), PositionAtCursor) .AddStyle("left", _openPosition.Left.ToPx(), PositionAtCursor) .Build(); + /// + /// Inline data attributes for positioning the menu at the cursor's location. + /// + private Dictionary PositionAttributes => new() + { + { "data-pc-x", _openPosition.Left.ToString(CultureInfo.InvariantCulture) }, + { "data-pc-y", _openPosition.Top.ToString(CultureInfo.InvariantCulture) }, + }; + /// /// The text shown for this menu. /// @@ -337,7 +343,7 @@ public MudMenu() ///
[Parameter] [Category(CategoryTypes.Menu.PopupBehavior)] - public bool Modal { get; set; } = true; + public bool Modal { get; set; } = MudGlobal.PopoverDefaults.ModalOverlay; /// /// The components within this menu. @@ -372,15 +378,23 @@ public MudMenu() /// internal bool GetDense() => Dense || ParentMenu?.GetDense() == true; + /// + /// Determines the positioning origin for the menu popover. + /// + /// + /// This establishes where the menu will appear relative to its activator or the cursor. + /// protected Origin GetAnchorOrigin() { if (AnchorOrigin is not null) { + // Use the defined anchor origin if set. return AnchorOrigin.Value; } if (ParentMenu is not null) { + // Sub-menus typically open to the right of their parent. return Origin.TopRight; } else if (PositionAtCursor) @@ -388,14 +402,25 @@ protected Origin GetAnchorOrigin() return Origin.TopLeft; } + // Default behavior for a top-level menu is to open below its activator. return Origin.BottomLeft; } + /// + /// Registers a child menu with this menu, allowing for hierarchical menu management. + /// This is crucial for controlling the open/close state of nested menus. + /// + /// The child to register. protected void RegisterChild(MudMenu child) { _subMenus.Add(child); } + /// + /// Unregisters a child menu from this menu. + /// This is called when a child menu is disposed or removed, maintaining accurate tracking of nested menus. + /// + /// The child to unregister. protected void UnregisterChild(MudMenu child) { _subMenus.Remove(child); @@ -404,6 +429,8 @@ protected void UnregisterChild(MudMenu child) protected override void OnInitialized() { base.OnInitialized(); + + // If this menu is a sub-menu, register it with its parent. ParentMenu?.RegisterChild(this); } @@ -417,8 +444,12 @@ protected Task OnOpenChanged(ParameterChangedEventArgs args) /// /// Closes this menu and any descendants if it's a nested menu. /// + /// + /// It ensures that all nested menus are also closed when a parent menu is closed. + /// public async Task CloseMenuAsync() { + // Discard any pending pointer actions so the menu doesn't re-open or try to close twice. CancelPendingActions(); // Recursively close all child menus. @@ -427,6 +458,7 @@ public async Task CloseMenuAsync() await child.CloseMenuAsync(); } + // Now close this menu itself. await _openState.SetValueAsync(false); await InvokeAsync(StateHasChanged); } @@ -434,6 +466,9 @@ public async Task CloseMenuAsync() /// /// Closes all menus in the hierarchy, starting from the top-most parent. /// + /// + /// This is useful for dismissing all open menus with a single action, such as clicking outside the menu area. + /// public async Task CloseAllMenusAsync() { // Traverse up the menu hierarchy to find the top-most parent. @@ -472,20 +507,24 @@ public async Task OpenMenuAsync(EventArgs args, bool transient = false) _isTransient = transient; - // Set the menu position if the event has cursor coordinates. + // Set the menu position to the cursor if the event has coordinates. if (args is MouseEventArgs mouseEventArgs) { _openPosition = (mouseEventArgs.PageY, mouseEventArgs.PageX); } + // Officially open the menu. await _openState.SetValueAsync(true); await InvokeAsync(StateHasChanged); } /// - /// Closes siblings before opening as a "mouse over" menu. - /// This is called in place of if the menu activator is implicitly rendered for the submenu. + /// Closes sibling menus before opening as a "mouse over" menu. + /// It prevents multiple sub-menus at the same level from being open simultaneously when hovering. /// + /// + /// This is called in place of if the menu activator is implicitly rendered for the submenu. + /// protected async Task OpenSubMenuAsync(EventArgs args) { // Close siblings (and self) first. @@ -534,6 +573,12 @@ public Task ToggleMenuAsync(EventArgs args) : OpenMenuAsync(args); } + /// + /// Determines if the menu should respond to hover events. + /// + /// + /// This prevents hover-related actions on devices that don't support traditional hovering (e.g., touchscreens). + /// private bool IsHoverable(PointerEventArgs args) { // If hover isn't explicitly enabled (or implicitly by being a submenu) there's no work to be done. @@ -554,58 +599,90 @@ private bool IsHoverable(PointerEventArgs args) /// /// Handles the pointer entering either the activator or the menu list. /// - internal async Task PointerEnterAsync(PointerEventArgs args) + /// + /// This initiates a hover delay before opening the menu to provide a more forgiving user experience. + /// + private async Task PointerEnterAsync(PointerEventArgs args) { _isPointerOver = true; + // Prevent conflicting actions. + CancelPendingActions(); + if (!IsHoverable(args)) { return; } - // Cancel any pending hide operation - _hideDebouncer.Cancel(); - - // Schedule the show operation with debouncing - await _showDebouncer.DebounceAsync(async () => + if (MudGlobal.MenuDefaults.HoverDelay > 0) { - if (!_openState.Value) + _hoverCts = new(); + + try + { + await Task.Delay(MudGlobal.MenuDefaults.HoverDelay, _hoverCts.Token); + } + catch (TaskCanceledException) { - await OpenSubMenuAsync(args); + // Hover action was canceled, meaning another action (like moving the pointer away) occurred. + return; } - }); + } + + if (!_openState.Value) + { + await OpenSubMenuAsync(args); + } } /// /// Handles the pointer leaving either the activator or the menu list. /// - internal async Task PointerLeaveAsync(PointerEventArgs args) + /// + /// This introduces a delay before closing the menu to allow smooth transitions between nested menus. + /// + private async Task PointerLeaveAsync(PointerEventArgs args) { _isPointerOver = false; - var isSubmenu = ParentMenu is not null; - if (!isSubmenu && ActivationEvent != MouseEvent.MouseOver) - { - return; // main menu that doesn't use mouseover - } + // Prevent conflicting actions. + CancelPendingActions(); + + // Only close if the menu is transient (e.g. hover-activated) and is hoverable. if (!_isTransient || !IsHoverable(args)) { return; } - // Cancel any pending show operation - _showDebouncer.Cancel(); - - // Schedule the hide operation with debouncing - await _hideDebouncer.DebounceAsync(async () => + // Add a delay if one is configured. + if (MudGlobal.MenuDefaults.HoverDelay > 0) { - if (!HasPointerOver(this)) + _leaveCts = new(); + + try { - await CloseMenuAsync(); + await Task.Delay(MudGlobal.MenuDefaults.HoverDelay, _leaveCts.Token); } - }); + catch (TaskCanceledException) + { + // Leave action was canceled, meaning the pointer re-entered the menu area. + return; + } + } + + // Close the menu only if the pointer is no longer over this menu or any of its sub-menus. + if (!HasPointerOver(this)) + { + await CloseMenuAsync(); + } } + /// + /// Recursively checks if the pointer is currently over this menu or any of its sub-menus. + /// + /// + /// This is crucial for determining when to close hover-activated menus. + /// protected bool HasPointerOver(MudMenu menu) { if (menu._isPointerOver) @@ -616,24 +693,35 @@ protected bool HasPointerOver(MudMenu menu) } /// - /// Use if another action is started or explicitly called. + /// Cancels any pending hover or leave actions. /// + /// + /// This is called when a new menu action is initiated, preventing conflicting or stale operations. + /// private void CancelPendingActions() { - _showDebouncer.Cancel(); - _hideDebouncer.Cancel(); + // ReSharper disable MethodHasAsyncOverload + // Cancels any ongoing hover-to-open or leave-to-close delays. + _leaveCts?.Cancel(); + _hoverCts?.Cancel(); + // ReSharper restore MethodHasAsyncOverload } /// /// Implementation of IActivatable.Activate, toggles the menu. /// + /// + /// This method serves as the entry point for activating the menu via an external activator. + /// void IActivatable.Activate(object activator, MouseEventArgs args) { + // Prevent activation if the activator button has a specific CSS class that marks it as non-activatable. if (activator is MudBaseButton activatorButton && (activatorButton.Class?.Contains("mud-no-activator") ?? false)) { return; } + ToggleMenuAsync(args).CatchAndLog(); } @@ -646,7 +734,11 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - CancelPendingActions(); + _hoverCts?.Cancel(); + _hoverCts?.Dispose(); + + _leaveCts?.Cancel(); + _leaveCts?.Dispose(); ParentMenu?.UnregisterChild(this); } diff --git a/src/MudBlazor/Components/NavMenu/MudNavMenu.razor.cs b/src/MudBlazor/Components/NavMenu/MudNavMenu.razor.cs index 41042f4b9f00..e06015ee0fa2 100644 --- a/src/MudBlazor/Components/NavMenu/MudNavMenu.razor.cs +++ b/src/MudBlazor/Components/NavMenu/MudNavMenu.razor.cs @@ -54,7 +54,7 @@ public partial class MudNavMenu : MudComponentBase /// /// /// Defaults to false. - /// Can be overridden by + /// Override with . /// When true, the theme border-radius value will be used. /// Only takes affect if is true. /// diff --git a/src/MudBlazor/Components/Paper/MudPaper.razor.cs b/src/MudBlazor/Components/Paper/MudPaper.razor.cs index 4b0a97394fd2..de5463dc6cdc 100644 --- a/src/MudBlazor/Components/Paper/MudPaper.razor.cs +++ b/src/MudBlazor/Components/Paper/MudPaper.razor.cs @@ -48,7 +48,7 @@ public partial class MudPaper : MudComponentBase /// /// /// Defaults to false. - /// Can be overridden by . + /// Override with .. /// When true, the border-radius is set to 0. /// [Parameter] diff --git a/src/MudBlazor/Components/Picker/MudPicker.razor.cs b/src/MudBlazor/Components/Picker/MudPicker.razor.cs index 56e018606624..53af655eab6c 100644 --- a/src/MudBlazor/Components/Picker/MudPicker.razor.cs +++ b/src/MudBlazor/Components/Picker/MudPicker.razor.cs @@ -160,7 +160,7 @@ protected MudPicker(Converter converter) : base(converter) { } /// /// /// Defaults to false. - /// Can be overridden by . + /// Override with .. /// [Parameter] [Category(CategoryTypes.FormComponent.PickerAppearance)] @@ -171,7 +171,7 @@ protected MudPicker(Converter converter) : base(converter) { } /// /// /// Defaults to false. - /// Can be overridden by . + /// Override with .. /// When true, the border-radius style is set to the theme's default value. /// [Parameter] @@ -431,7 +431,7 @@ public IMask? Mask ///
[Parameter] [Category(CategoryTypes.FormComponent.Behavior)] - public bool Modal { get; set; } = true; + public bool Modal { get; set; } = MudGlobal.PopoverDefaults.ModalOverlay; /// /// The location the popover opens, relative to its container. diff --git a/src/MudBlazor/Components/Popover/MudPopover.razor.cs b/src/MudBlazor/Components/Popover/MudPopover.razor.cs index 63bd69317e3f..2c250f645368 100644 --- a/src/MudBlazor/Components/Popover/MudPopover.razor.cs +++ b/src/MudBlazor/Components/Popover/MudPopover.razor.cs @@ -98,7 +98,7 @@ internal Direction ConvertDirection(Direction direction) /// /// /// Defaults to false. - /// Can be overridden by . + /// Override with .. /// When true, the CSS border-radius is set to 0. /// [Parameter] diff --git a/src/MudBlazor/Components/Progress/MudProgressCircular.razor.cs b/src/MudBlazor/Components/Progress/MudProgressCircular.razor.cs index d14eecf5ff04..a54606c90f32 100644 --- a/src/MudBlazor/Components/Progress/MudProgressCircular.razor.cs +++ b/src/MudBlazor/Components/Progress/MudProgressCircular.razor.cs @@ -71,7 +71,7 @@ public partial class MudProgressCircular : MudComponentBase /// /// /// Defaults to false. - /// Can be overridden by + /// Override with . /// When true, the CSS stroke-linecap is set to round. /// [Parameter] diff --git a/src/MudBlazor/Components/Progress/MudProgressLinear.razor.cs b/src/MudBlazor/Components/Progress/MudProgressLinear.razor.cs index d22acfe76300..fe1696249290 100644 --- a/src/MudBlazor/Components/Progress/MudProgressLinear.razor.cs +++ b/src/MudBlazor/Components/Progress/MudProgressLinear.razor.cs @@ -80,7 +80,7 @@ public partial class MudProgressLinear : MudComponentBase /// /// /// Defaults to false. - /// Can be overridden by + /// Override with . /// When true, the CSS border-radius is set to the theme's default value. /// [Parameter] diff --git a/src/MudBlazor/Components/Select/MudSelect.razor.cs b/src/MudBlazor/Components/Select/MudSelect.razor.cs index 67e30cf05c1c..d95d8eb73129 100644 --- a/src/MudBlazor/Components/Select/MudSelect.razor.cs +++ b/src/MudBlazor/Components/Select/MudSelect.razor.cs @@ -287,7 +287,7 @@ private async Task SelectLastItem() ///
[Parameter] [Category(CategoryTypes.FormComponent.ListBehavior)] - public bool Modal { get; set; } = true; + public bool Modal { get; set; } = MudGlobal.PopoverDefaults.ModalOverlay; /// /// The content within this component, typically a list of components. @@ -1042,7 +1042,7 @@ public override ValueTask SelectRangeAsync(int pos1, int pos2) /// Occurs when the Clear button has been clicked. /// /// - /// This is the first event raised when the clear button is clicked. + /// This is the first event raised when the clear button is clicked. /// The are cleared and the event is raised. /// protected async ValueTask SelectClearButtonClickHandlerAsync(MouseEventArgs e) @@ -1109,9 +1109,9 @@ protected async Task SetCustomizedTextAsync(string text, bool updateValue = true /// The icon to display whether all, none, or some items are selected. /// /// - /// Only applies when is true. + /// Only applies when is true. /// If all items are selected, is returned. - /// If no items are selected, is returned. + /// If no items are selected, is returned. /// Otherwise, is returned. /// protected string SelectAllCheckBoxIcon diff --git a/src/MudBlazor/Components/Snackbar/InternalComponents/SnackbarMessageText.razor.cs b/src/MudBlazor/Components/Snackbar/InternalComponents/SnackbarMessageText.razor.cs index c889a4bfdc12..43298a3c7afc 100644 --- a/src/MudBlazor/Components/Snackbar/InternalComponents/SnackbarMessageText.razor.cs +++ b/src/MudBlazor/Components/Snackbar/InternalComponents/SnackbarMessageText.razor.cs @@ -10,7 +10,7 @@ namespace MudBlazor.Components.Snackbar.InternalComponents; public partial class SnackbarMessageText : ComponentBase { /// - /// Gets or sets the plain text message to be displayed. + /// Plain text message to be displayed. /// /// /// This property is used to pass a plain string message. It does not support HTML or UI fragments. diff --git a/src/MudBlazor/Components/Snackbar/Snackbar.cs b/src/MudBlazor/Components/Snackbar/Snackbar.cs index d4cc3bd531a6..cc0abe55443d 100644 --- a/src/MudBlazor/Components/Snackbar/Snackbar.cs +++ b/src/MudBlazor/Components/Snackbar/Snackbar.cs @@ -92,9 +92,9 @@ public void ForceClose() /// /// Transitions the snackbar to the specified state. /// - /// The state to transition to - /// Whether the transition should be animated or instant - /// Whether the transition, if animated, can be cancelled + /// The state to transition to. + /// The transition should be animated or instant. + /// The transition, if animated, can be cancelled. private void TransitionTo(SnackbarState state, bool animate = true, bool cancellable = true) { // A new non-cancellable transition takes priority and will force a resume. diff --git a/src/MudBlazor/Components/Table/MudTableBase.cs b/src/MudBlazor/Components/Table/MudTableBase.cs index 83a9983236b4..b1fb7550d41c 100644 --- a/src/MudBlazor/Components/Table/MudTableBase.cs +++ b/src/MudBlazor/Components/Table/MudTableBase.cs @@ -73,7 +73,7 @@ public abstract class MudTableBase : MudComponentBase /// /// /// Defaults to false. - /// Can be overridden by . + /// Override with .. /// [Parameter] [Category(CategoryTypes.Table.Appearance)] diff --git a/src/MudBlazor/Components/Table/MudTablePager.razor.cs b/src/MudBlazor/Components/Table/MudTablePager.razor.cs index b3957c19fe7d..4aec26194a4d 100644 --- a/src/MudBlazor/Components/Table/MudTablePager.razor.cs +++ b/src/MudBlazor/Components/Table/MudTablePager.razor.cs @@ -117,15 +117,15 @@ private string Info var firstItem = (filteredItemsCount == 0 ? 0 : (Table?.CurrentPage * Table?.RowsPerPage) + 1) ?? 0; var lastItem = Math.Min((Table?.CurrentPage + 1) * Table?.RowsPerPage ?? 0, filteredItemsCount); - if (InfoFormat.Contains("{first_item}") || InfoFormat.Contains("{last_item}") || InfoFormat.Contains("{all_items}")) + if (string.IsNullOrEmpty(InfoFormat)) { - return InfoFormat - .Replace("{first_item}", $"{firstItem}") - .Replace("{last_item}", $"{lastItem}") - .Replace("{all_items}", $"{filteredItemsCount:N0}"); + return Localizer[LanguageResource.MudDataGridPager_InfoFormat, firstItem, lastItem, $"{filteredItemsCount:N0}"]; } - return Localizer[LanguageResource.MudDataGridPager_InfoFormat, firstItem, lastItem, $"{filteredItemsCount:N0}"]; + return InfoFormat + .Replace("{first_item}", $"{firstItem}") + .Replace("{last_item}", $"{lastItem}") + .Replace("{all_items}", $"{filteredItemsCount:N0}"); } } diff --git a/src/MudBlazor/Components/TableSimple/MudSimpleTable.razor.cs b/src/MudBlazor/Components/TableSimple/MudSimpleTable.razor.cs index 98db9d99beda..50b6fc31839a 100644 --- a/src/MudBlazor/Components/TableSimple/MudSimpleTable.razor.cs +++ b/src/MudBlazor/Components/TableSimple/MudSimpleTable.razor.cs @@ -49,7 +49,7 @@ public partial class MudSimpleTable : MudComponentBase /// /// /// Defaults to false. - /// Can be overridden by . + /// Override with .. /// [Parameter] [Category(CategoryTypes.SimpleTable.Appearance)] diff --git a/src/MudBlazor/Components/Tabs/MudTabs.razor b/src/MudBlazor/Components/Tabs/MudTabs.razor index 42b6cd25a2d0..d5e7b0a13c1d 100644 --- a/src/MudBlazor/Components/Tabs/MudTabs.razor +++ b/src/MudBlazor/Components/Tabs/MudTabs.razor @@ -13,43 +13,44 @@ } @if (_showScrollButtons) { -
+
}
-
- @foreach (MudTabPanel panel in _panels) + @if (EnableDragAndDrop) { - @if (panel.TabContent == null && panel.TabWrapperContent == null) - { - - @RenderTab(panel) - - } - else - { -
- @if (panel.TabWrapperContent is null) + + + + @if (!HideSlider && IsSliderPositionDetermined) { - @RenderTab(panel) +
} - else - { - @panel.TabWrapperContent(RenderTab(panel)) - } -
- } + + + @RenderTab(context) + + } - @if (!HideSlider && IsSliderPositionDetermined) + else { -
- } -
+
+ @RenderTabSection() + @if (!HideSlider && IsSliderPositionDetermined) + { +
+ } +
+ }
@if (_showScrollButtons) { -
+
} @@ -72,7 +73,33 @@
@code { -#nullable enable + #nullable enable + RenderFragment RenderTabSection() => + @ + @foreach (MudTabPanel panel in _panels) + { + @if (panel.TabContent == null && panel.TabWrapperContent == null) + { + + @RenderTab(panel) + + } + else + { +
+ @if (panel.TabWrapperContent is null) + { + @RenderTab(panel) + } + else + { + @panel.TabWrapperContent(RenderTab(panel)) + } +
+ } + } +
; + RenderFragment RenderTab(MudTabPanel panel) => @
ActivatePanel(panel, e, false) )> @if (TabPanelHeaderPosition == TabHeaderPosition.Before && TabPanelHeader != null) { @@ -108,4 +135,4 @@
}
; -} \ No newline at end of file +} diff --git a/src/MudBlazor/Components/Tabs/MudTabs.razor.cs b/src/MudBlazor/Components/Tabs/MudTabs.razor.cs index 8ed877be1849..51d1c45bf627 100644 --- a/src/MudBlazor/Components/Tabs/MudTabs.razor.cs +++ b/src/MudBlazor/Components/Tabs/MudTabs.razor.cs @@ -9,6 +9,7 @@ using MudBlazor.Interop; using MudBlazor.Services; using MudBlazor.Utilities; +using MudBlazor.Utilities.Throttle; #nullable enable namespace MudBlazor @@ -36,6 +37,8 @@ public partial class MudTabs : MudComponentBase, IAsyncDisposable private IResizeObserver? _resizeObserver = null; + private readonly ThrottleDispatcher _throttleDispatcher; + /// /// Displays text right-to-left. /// @@ -48,6 +51,14 @@ public partial class MudTabs : MudComponentBase, IAsyncDisposable [Inject] private IResizeObserverFactory _resizeObserverFactory { get; set; } = null!; + /// + /// Enables drag-and-drop re-ordering of tabs. + /// + /// Defaults to false. + [Parameter] + [Category(CategoryTypes.Tabs.Behavior)] + public bool EnableDragAndDrop { get; set; } + /// /// Persists the content of tabs when they are not visible. /// @@ -65,7 +76,7 @@ public partial class MudTabs : MudComponentBase, IAsyncDisposable /// /// /// Defaults to false. - /// Can be overridden by + /// Override with . /// When true, the border-radius style is set to the theme's default value. /// [Parameter] @@ -362,7 +373,7 @@ public int ActivePanelIndex ///
public IReadOnlyList Panels { get; private set; } - private List _panels; + internal List _panels; /// /// The custom content added before or after the list of tabs. @@ -445,6 +456,7 @@ public int ActivePanelIndex public MudTabs() { + _throttleDispatcher = new ThrottleDispatcher(500); _panels = new List(); Panels = _panels.AsReadOnly(); } @@ -462,6 +474,7 @@ protected override void OnParametersSet() _resizeObserver ??= _resizeObserverFactory.Create(); Rerender(); + StateHasChanged(); } protected override async Task OnAfterRenderAsync(bool firstRender) @@ -478,8 +491,6 @@ protected override async Task OnAfterRenderAsync(bool firstRender) _resizeObserver.OnResized += OnResized; - Rerender(); - StateHasChanged(); ActivatePanel(ActivePanelIndex); _isRendered = true; @@ -621,7 +632,8 @@ private async void ActivatePanel(MudTabPanel panel, MouseEventArgs? ev, bool ign SetSliderState(); SetScrollButtonVisibility(); SetScrollabilityStates(); - StateHasChanged(); + Rerender(); + await InvokeAsync(StateHasChanged); } } @@ -699,6 +711,13 @@ private int GetTabSortExpression(MudTabPanel a, MudTabPanel b) .AddClass($"mud-tab-slider-vertical-reverse", Position == Position.Right || Position == Position.Start && RightToLeft || Position == Position.End && !RightToLeft) .Build(); + protected string DropZoneClassnames => + new CssBuilder("mud-tabs-dropzone") + .AddClass("d-flex", !IsVerticalTabs()) + .AddClass($"mud-tabs-vertical", IsVerticalTabs()) + .AddClass("flex-grow-1") + .Build(); + protected string MaxHeightStyles => new StyleBuilder() .AddStyle("max-height", MaxHeight.ToPx(), MaxHeight != null) @@ -706,20 +725,20 @@ private int GetTabSortExpression(MudTabPanel a, MudTabPanel b) protected string SliderStyle => RightToLeft ? new StyleBuilder() - .AddStyle("width", $"{_sliderSizePercentage}%", Position is Position.Top or Position.Bottom) - .AddStyle("right", $"{_sliderPositionPercentage}%", Position is Position.Top or Position.Bottom) + .AddStyle("width", _sliderSizePercentage.ToPercent(), Position is Position.Top or Position.Bottom) + .AddStyle("right", _sliderPositionPercentage.ToPercent(), Position is Position.Top or Position.Bottom) .AddStyle("transition", SliderAnimation ? "right .3s cubic-bezier(.64,.09,.08,1);" : "none", Position is Position.Top or Position.Bottom) .AddStyle("transition", SliderAnimation ? "top .3s cubic-bezier(.64,.09,.08,1);" : "none", IsVerticalTabs()) - .AddStyle("height", $"{_sliderSizePercentage}%", IsVerticalTabs()) - .AddStyle("top", $"{_sliderPositionPercentage}%", IsVerticalTabs()) + .AddStyle("height", _sliderSizePercentage.ToPercent(), IsVerticalTabs()) + .AddStyle("top", _sliderPositionPercentage.ToPercent(), IsVerticalTabs()) .Build() : new StyleBuilder() - .AddStyle("width", $"{_sliderSizePercentage}%", Position is Position.Top or Position.Bottom) - .AddStyle("left", $"{_sliderPositionPercentage}%", Position is Position.Top or Position.Bottom) + .AddStyle("width", _sliderSizePercentage.ToPercent(), Position is Position.Top or Position.Bottom) + .AddStyle("left", _sliderPositionPercentage.ToPercent(), Position is Position.Top or Position.Bottom) .AddStyle("transition", SliderAnimation ? "left .3s cubic-bezier(.64,.09,.08,1);" : "none", Position is Position.Top or Position.Bottom) .AddStyle("transition", SliderAnimation ? "top .3s cubic-bezier(.64,.09,.08,1);" : "none", IsVerticalTabs()) - .AddStyle("height", $"{_sliderSizePercentage}%", IsVerticalTabs()) - .AddStyle("top", $"{_sliderPositionPercentage}%", IsVerticalTabs()) + .AddStyle("height", _sliderSizePercentage.ToPercent(), IsVerticalTabs()) + .AddStyle("top", _sliderPositionPercentage.ToPercent(), IsVerticalTabs()) .Build(); private bool IsVerticalTabs() @@ -1042,5 +1061,34 @@ private void SetScrollabilityStates() } #endregion + + internal void ItemUpdated(MudItemDropInfo dropItem) + { + if (dropItem.Item is null) + { + return; + } + + // get the old index where this item was at + var oldIndex = _panels.IndexOf(dropItem.Item); + // get the new index in _panels using IndexInZone + var newIndex = dropItem.IndexInZone; + + // remove the item from the old index + _panels.RemoveAt(oldIndex); + + // insert the item at the new index + if (newIndex < _panels.Count) + { + _panels.Insert(newIndex, dropItem.Item); + } + else + { + _panels.Add(dropItem.Item); + } + + // Set the dragged tab as active + ActivatePanel(dropItem.Item); + } } } diff --git a/src/MudBlazor/Components/TextField/MudTextField.razor b/src/MudBlazor/Components/TextField/MudTextField.razor index a13aa88bcdca..d4b3ea230f36 100644 --- a/src/MudBlazor/Components/TextField/MudTextField.razor +++ b/src/MudBlazor/Components/TextField/MudTextField.razor @@ -17,7 +17,8 @@ Disabled="@GetDisabledState()" Margin="@Margin" Required="@Required" - ForId="@InputElementId"> + ForId="@InputElementId" + @onclick="HandleContainerClick"> @if (_mask == null) diff --git a/src/MudBlazor/Components/TimePicker/MudTimePicker.razor.cs b/src/MudBlazor/Components/TimePicker/MudTimePicker.razor.cs index 370605c02d48..2f1f5e3ca92d 100644 --- a/src/MudBlazor/Components/TimePicker/MudTimePicker.razor.cs +++ b/src/MudBlazor/Components/TimePicker/MudTimePicker.razor.cs @@ -29,7 +29,6 @@ public MudTimePicker() : base(new DefaultConverter()) Converter.SetFunc = OnSet; ((DefaultConverter)Converter).Format = Format24Hours; AdornmentIcon = Icons.Material.Filled.AccessTime; - AdornmentAriaLabel = "Open Time Picker"; } private string OnSet(TimeSpan? timespan) @@ -548,6 +547,7 @@ private string GetPointerHeight() protected override void OnInitialized() { base.OnInitialized(); + AdornmentAriaLabel ??= Localizer[LanguageResource.MudTimePicker_Open]; UpdateTimeSetFromTime(); _currentView = OpenTo; _initialHour = _timeSet.Hour; diff --git a/src/MudBlazor/Components/Toggle/MudToggleGroup.razor.cs b/src/MudBlazor/Components/Toggle/MudToggleGroup.razor.cs index 3483fce9eed7..36ec4cf20390 100644 --- a/src/MudBlazor/Components/Toggle/MudToggleGroup.razor.cs +++ b/src/MudBlazor/Components/Toggle/MudToggleGroup.razor.cs @@ -79,7 +79,8 @@ public MudToggleGroup() .AddClass("mud-toggle-group-vertical", Vertical) .AddClass($"mud-toggle-group-size-{Size.ToDescriptionString()}") .AddClass("mud-toggle-group-rtl", RightToLeft) - .AddClass($"border mud-border-{Color.ToDescriptionString()} border-solid", Outlined) + .AddClass($"mud-toggle-group-{Color.ToDescriptionString()}") + .AddClass("mud-toggle-group-outlined", Outlined) .AddClass("mud-disabled", Disabled) .AddClass(Class) .Build(); diff --git a/src/MudBlazor/Components/Toggle/MudToggleItem.razor b/src/MudBlazor/Components/Toggle/MudToggleItem.razor index 3e9c99ecda02..3a399c8fb2b5 100644 --- a/src/MudBlazor/Components/Toggle/MudToggleItem.razor +++ b/src/MudBlazor/Components/Toggle/MudToggleItem.razor @@ -8,17 +8,17 @@ Class="@Classname" Style="@Style" OnClick="HandleOnClickAsync" - Size="@(Parent?.Size ?? Size.Medium)" - Color="@(Parent?.Color ?? Color.Default)" + Size="AssertedParent.Size" + Color="AssertedParent.Color" Variant="@(Selected ? Variant.Filled : Variant.Outlined)" - Disabled="Disabled || (Parent?.Disabled ?? false)" - Ripple="Parent?.Ripple == true"> + Disabled="Disabled || AssertedParent.Disabled" + Ripple="AssertedParent.Ripple"> @if (!string.IsNullOrWhiteSpace(GetCurrentIcon())) { + Size="AssertedParent.Size" /> } @if (ChildContent is not null) { diff --git a/src/MudBlazor/Components/Toggle/MudToggleItem.razor.cs b/src/MudBlazor/Components/Toggle/MudToggleItem.razor.cs index f1df05b500d0..069045f6d6e1 100644 --- a/src/MudBlazor/Components/Toggle/MudToggleItem.razor.cs +++ b/src/MudBlazor/Components/Toggle/MudToggleItem.razor.cs @@ -19,27 +19,32 @@ namespace MudBlazor public partial class MudToggleItem : MudComponentBase, IDisposable { protected string Classname => new CssBuilder("mud-toggle-item") - .AddClass(Parent?.SelectedClass, Selected && !string.IsNullOrEmpty(Parent?.SelectedClass)) + .AddClass(AssertedParent.SelectedClass, Selected && !string.IsNullOrEmpty(AssertedParent.SelectedClass)) .AddClass("mud-toggle-item-selected", Selected) - .AddClass("mud-toggle-item-vertical", Parent?.Vertical == true) - .AddClass("mud-toggle-item-delimiter", Parent?.Delimiters == true) - .AddClass("mud-toggle-item-fixed", Parent?.CheckMark == true && Parent?.FixedContent == true) - .AddClass($"mud-toggle-item-size-{(Parent?.Size ?? Size.Medium).ToDescriptionString()}") - .AddClass("mud-ripple", Parent?.Ripple == true) + .AddClass("mud-toggle-item-vertical", AssertedParent.Vertical) + .AddClass("mud-toggle-item-delimiter", AssertedParent.Delimiters) + .AddClass("mud-toggle-item-fixed", AssertedParent.CheckMark && AssertedParent.FixedContent) + .AddClass($"mud-toggle-item-size-{AssertedParent.Size.ToDescriptionString()}") + .AddClass("mud-ripple", AssertedParent.Ripple) .AddClass("mud-typography-input") .AddClass(Class) .Build(); protected string CheckMarkClassname => new CssBuilder("mud-toggle-item-check-icon") - .AddClass(Parent?.CheckMarkClass) + .AddClass(AssertedParent.CheckMarkClass) .Build(); /// - /// The hosting this item. + /// The hosting this item if one exists. /// [CascadingParameter] public MudToggleGroup? Parent { get; set; } + /// + /// The hosting this item, but validated to be non-null. + /// + private MudToggleGroup AssertedParent => Parent ?? throw new InvalidOperationException($"{nameof(MudToggleItem)} must be used within a {nameof(MudToggleGroup)}."); + /// /// Prevents the user from interacting with this item. /// @@ -105,7 +110,7 @@ public partial class MudToggleItem : MudComponentBase, IDisposable private string? GetCurrentIcon() { - if (Parent?.CheckMark != true) + if (!AssertedParent.CheckMark) { return null; } @@ -115,7 +120,7 @@ public partial class MudToggleItem : MudComponentBase, IDisposable return SelectedIcon; } - if (UnselectedIcon is null && Parent?.FixedContent == true) + if (UnselectedIcon is null && AssertedParent.FixedContent) { return Icons.Custom.Uncategorized.Empty; } @@ -123,11 +128,10 @@ public partial class MudToggleItem : MudComponentBase, IDisposable return UnselectedIcon; } - /// protected override void OnInitialized() { base.OnInitialized(); - Parent?.Register(this); + AssertedParent.Register(this); } /// @@ -137,6 +141,7 @@ protected virtual void Dispose(bool disposing) { if (disposing) { + // Don't assume we have a parent during disposal. Parent?.Unregister(this); } } @@ -162,10 +167,7 @@ public void SetSelected(bool selected) protected async Task HandleOnClickAsync() { - if (Parent is not null) - { - await Parent.ToggleItemAsync(this); - } + await AssertedParent.ToggleItemAsync(this); } } } diff --git a/src/MudBlazor/Components/Tooltip/MudTooltip.razor.cs b/src/MudBlazor/Components/Tooltip/MudTooltip.razor.cs index 6f646271c9b7..5a052cdbf95d 100644 --- a/src/MudBlazor/Components/Tooltip/MudTooltip.razor.cs +++ b/src/MudBlazor/Components/Tooltip/MudTooltip.razor.cs @@ -38,7 +38,7 @@ public MudTooltip() .Build(); /// - /// Whether the display should be right to left + /// Displays content right-to-left. /// [CascadingParameter(Name = "RightToLeft")] public bool RightToLeft { get; set; } @@ -208,7 +208,7 @@ public MudTooltip() /// Gets whether the tooltip should be shown. /// /// - /// The tooltip will be displayed if it is not disabled, not already visible, and either or is specified. + /// The tooltip will be displayed if not disabled, not already visible, and either or is specified. /// internal bool ShowToolTip() { diff --git a/src/MudBlazor/Components/TreeView/MudTreeView.razor.cs b/src/MudBlazor/Components/TreeView/MudTreeView.razor.cs index aeb7abc59008..cdda91aa2862 100644 --- a/src/MudBlazor/Components/TreeView/MudTreeView.razor.cs +++ b/src/MudBlazor/Components/TreeView/MudTreeView.razor.cs @@ -7,7 +7,7 @@ namespace MudBlazor { #nullable enable /// - /// A hierarchical tree of expandable items with optional value selection. + /// An extensively customizable tree view component for displaying hierarchical data, featuring item selection, lazy-loading, and templating support. /// /// The type of item to display. /// diff --git a/src/MudBlazor/Components/TreeView/MudTreeViewItemToggleButton.razor b/src/MudBlazor/Components/TreeView/MudTreeViewItemToggleButton.razor index 63d26e283142..701fd897eb8c 100644 --- a/src/MudBlazor/Components/TreeView/MudTreeViewItemToggleButton.razor +++ b/src/MudBlazor/Components/TreeView/MudTreeViewItemToggleButton.razor @@ -1,16 +1,18 @@ -@namespace MudBlazor +@using MudBlazor.Resources +@namespace MudBlazor @inherits MudComponentBase +@inject InternalMudLocalizer Localizer
- @if (Visible) { - + Style="@Style" + aria-label="@(Expanded ? @Localizer[LanguageResource.MudTreeView_CollapseItem] : @Localizer[LanguageResource.MudTreeView_ExpandItem])" /> }
diff --git a/src/MudBlazor/Components/Virtualize/MudVirtualize.razor.cs b/src/MudBlazor/Components/Virtualize/MudVirtualize.razor.cs index 42c315503b55..940de795e09c 100644 --- a/src/MudBlazor/Components/Virtualize/MudVirtualize.razor.cs +++ b/src/MudBlazor/Components/Virtualize/MudVirtualize.razor.cs @@ -22,13 +22,13 @@ public partial class MudVirtualize : ComponentBase public bool Enabled { get; set; } /// - /// Gets or sets the item template for the list. + /// Item template for the list. /// [Parameter] public RenderFragment? ChildContent { get; set; } /// - /// Gets or sets the template for the items that have not yet been loaded in memory. + /// Template for the items that have not yet been loaded in memory. /// [Parameter] public RenderFragment? Placeholder { get; set; } @@ -40,13 +40,13 @@ public partial class MudVirtualize : ComponentBase public RenderFragment? NoRecordsContent { get; set; } /// - /// Gets or sets the fixed item source. + /// Fixed item source. /// [Parameter] public ICollection? Items { get; set; } /// - /// Gets or sets the function providing items to the list. + /// Function providing items to the list. /// [Parameter] public ItemsProviderDelegate? ItemsProvider { get; set; } diff --git a/src/MudBlazor/Extensions/ElementReferenceExtensions.cs b/src/MudBlazor/Extensions/ElementReferenceExtensions.cs index 0327ca85b78f..0bbea9ecedb5 100644 --- a/src/MudBlazor/Extensions/ElementReferenceExtensions.cs +++ b/src/MudBlazor/Extensions/ElementReferenceExtensions.cs @@ -70,6 +70,7 @@ public static ValueTask RemoveDefaultPreventingHandlers(this ElementReference el DotNetObjectReference obj) where T : class => elementReference.GetJSRuntime()?.InvokeVoidAsync("mudElementRef.addOnBlurEvent", elementReference, obj) ?? ValueTask.CompletedTask; + [Obsolete("Use mudElementRef.removeOnBlurEvent via js invoke instead")] public static ValueTask MudDetachBlurEventWithJS<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>( this ElementReference elementReference, DotNetObjectReference obj) where T : class => diff --git a/src/MudBlazor/Extensions/WebUnitsExtensions.cs b/src/MudBlazor/Extensions/WebUnitsExtensions.cs index 33352d9bde33..20e46c5af77e 100644 --- a/src/MudBlazor/Extensions/WebUnitsExtensions.cs +++ b/src/MudBlazor/Extensions/WebUnitsExtensions.cs @@ -15,5 +15,12 @@ public static class WebUnitsExtensions public static string ToPx(this long? val) => val != null ? val.Value.ToPx() : string.Empty; public static string ToPx(this double val) => $"{val.ToString("0.##", CultureInfo.InvariantCulture)}px"; public static string ToPx(this double? val) => val != null ? val.Value.ToPx() : string.Empty; + + public static string ToPercent(this int val) => $"{val}%"; + public static string ToPercent(this int? val) => val != null ? val.Value.ToPercent() : string.Empty; + public static string ToPercent(this long val) => $"{val}%"; + public static string ToPercent(this long? val) => val != null ? val.Value.ToPercent() : string.Empty; + public static string ToPercent(this double val) => $"{val.ToString("0.##", CultureInfo.InvariantCulture)}%"; + public static string ToPercent(this double? val) => val != null ? val.Value.ToPercent() : string.Empty; } } diff --git a/src/MudBlazor/Resources/LanguageResource.resx b/src/MudBlazor/Resources/LanguageResource.resx index af2bcd4b992c..fc2d9ddb2f19 100644 --- a/src/MudBlazor/Resources/LanguageResource.resx +++ b/src/MudBlazor/Resources/LanguageResource.resx @@ -124,7 +124,7 @@ Clear - Add Filter + Add filter Cancel @@ -136,22 +136,22 @@ Save - Hide All + Hide all - Show All + Show all Columns - Expand All Groups + Expand all groups - Collapse All Groups + Collapse all groups - Refresh Data + Refresh data true @@ -247,10 +247,10 @@ Value - Move Down + Move down - Move Up + Move up Sort @@ -259,13 +259,13 @@ Toggle {0} - Close Alert + Close - Close Chip + Close - Close Picker + Close Spectrum @@ -276,17 +276,20 @@ Palette - - Toggle Collection + + Show swatches + + + Hide swatches - Hue Slider + Hue slider - Alpha Slider + Alpha slider - Switch Mode + Switch mode Previous year {0} @@ -319,7 +322,7 @@ Previous page - {0} Rating + {0} rating Index {0} @@ -331,7 +334,7 @@ Previous - Close dialog + Close Clear @@ -343,7 +346,7 @@ Increment - Table of Contents + Contents First page @@ -358,7 +361,7 @@ Previous page - Close snackbar + Close Rows per page: @@ -385,31 +388,31 @@ Complete - Clear Filter + Clear filter - Open Filters + Open filters - Toggle Group Expansion + Toggle group - Remove Filter + Remove filter - First Page + First page - Previous Page + Previous page - Next Page + Next page - Last Page + Last page - Show Column Options + Column options Not a valid boolean @@ -450,4 +453,22 @@ More + + Expand + + + Collapse + + + Open + + + Open + + + Open + + + File '{0}' exceeds the maximum allowed size of {1} bytes. + \ No newline at end of file diff --git a/src/MudBlazor/Services/MudGlobal.cs b/src/MudBlazor/Services/MudGlobal.cs index 8317df1077a1..42b6a47dad69 100644 --- a/src/MudBlazor/Services/MudGlobal.cs +++ b/src/MudBlazor/Services/MudGlobal.cs @@ -129,6 +129,15 @@ public static class PopoverDefaults /// The amount of drop shadow to apply to . ///
public static int Elevation { get; set; } = 8; + + /// + /// Prevents interaction with background elements. + /// + /// + /// Only applies to components that use a in conjunction with a + /// to close the popover when a user clicks outside, such as . + /// + public static bool ModalOverlay { get; set; } = true; } /// @@ -152,7 +161,7 @@ public static class StackDefaults public static class TooltipDefaults { /// - /// The amount of time in milliseconds to wait from opening the before beginning to perform the transition. + /// The amount of time in milliseconds to wait from opening the before beginning to perform the transition. /// public static TimeSpan Delay { get; set; } = TransitionDefaults.Delay; @@ -175,7 +184,7 @@ public static class TransitionDefaults public static TimeSpan Delay { get; set; } = TimeSpan.Zero; /// - /// The amount of time in milliseconds to wait from opening the popover before beginning to perform the transition. + /// The amount of time in milliseconds to wait from opening the popover before beginning to perform the transition. /// public static TimeSpan Duration { get; set; } = TimeSpan.FromMilliseconds(251); } diff --git a/src/MudBlazor/Services/Version.cs b/src/MudBlazor/Services/Version.cs new file mode 100644 index 000000000000..cc3776e201dc --- /dev/null +++ b/src/MudBlazor/Services/Version.cs @@ -0,0 +1,16 @@ +// Copyright (c) MudBlazor 2021 +// MudBlazor licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace MudBlazor; + +/// +/// Provides metadata about the MudBlazor library. +/// +public static class Metadata +{ + /// + /// The current version number of MudBlazor. + /// + public static string Version { get; } = typeof(Metadata).Assembly.GetName().Version?.ToString(3) ?? "unknown"; +} diff --git a/src/MudBlazor/Styles/components/_input.scss b/src/MudBlazor/Styles/components/_input.scss index b795a92f12dd..83a21fdcbc4b 100644 --- a/src/MudBlazor/Styles/components/_input.scss +++ b/src/MudBlazor/Styles/components/_input.scss @@ -392,12 +392,9 @@ &.mud-input-root-filled { box-sizing: border-box; - margin-top: 0; + margin-top: 27px; margin-bottom: 0; - padding: 27px 12px 10px; - mask-image: linear-gradient(to bottom, - transparent 27px, - black 27px); + padding: 0px 12px 10px; &.mud-input-root-margin-dense { padding-top: 23px; @@ -460,12 +457,9 @@ &.mud-input-root-outlined { box-sizing: border-box; - margin-top: 0; + margin-top: 18.5px; margin-bottom: 0; - padding: 18.5px 14px; - mask-image: linear-gradient(to bottom, - transparent 18.5px, - black 18.5px); + padding: 0px 18.5px 14px; &.mud-input-root:-webkit-autofill { border-radius: inherit; @@ -545,3 +539,7 @@ .mud-picker .mud-shrink .mud-range-input-separator { visibility: visible; } + +.mud-input-input-control { + user-select: none; +} diff --git a/src/MudBlazor/Styles/components/_menu.scss b/src/MudBlazor/Styles/components/_menu.scss index e74b2503307b..61726a47d048 100644 --- a/src/MudBlazor/Styles/components/_menu.scss +++ b/src/MudBlazor/Styles/components/_menu.scss @@ -28,6 +28,7 @@ .mud-menu-list { padding: 4px 0; + min-width: 112px; > .mud-menu { width: 100%; @@ -84,3 +85,12 @@ .mud-menu-list:has(.mud-menu-item-icon) .mud-menu-item:not(:has(.mud-menu-item-icon)) .mud-menu-item-text { margin-inline-start: 36px; } + +.mud-menu-list:has(.mud-menu-submenu-icon) .mud-menu-item:not(:has(.mud-menu-submenu-icon)) .mud-menu-item-text { + margin-inline-end: 36px; +} + +// Prevent menu item hover effects peeking through rounded popovers. +.mud-popover:has(> .mud-menu-list) { + overflow: hidden; +} diff --git a/src/MudBlazor/Styles/components/_togglegroup.scss b/src/MudBlazor/Styles/components/_togglegroup.scss index 16240be3ea1a..1bc94b8abc6b 100644 --- a/src/MudBlazor/Styles/components/_togglegroup.scss +++ b/src/MudBlazor/Styles/components/_togglegroup.scss @@ -4,47 +4,56 @@ display: grid; overflow: hidden; box-sizing: border-box; - border-width: 0; border-radius: var(--mud-default-borderradius); & > .mud-toggle-item { box-shadow: none; border-width: inherit; - border-style: none; border-color: inherit; border-radius: 0; } } -.mud-toggle-group-horizontal { - &:not(.mud-toggle-group-rtl) { - > .mud-toggle-item:not(:first-child), > :not(:first-child) .mud-toggle-item { - &.mud-toggle-item-delimiter { - border-left-style: solid !important; - } +.mud-toggle-group-outlined { + border-width: 1px; + border-color: rgb(from var(--mud-palette-text-primary) r g b / var(--mud-palette-border-opacity)); - margin-left: -1px; + @each $color in $mud-palette-colors { + &.mud-toggle-group-#{$color} { + border-color: rgb(from var(--mud-palette-#{$color}) r g b / var(--mud-palette-border-opacity)); } } - &.mud-toggle-group-rtl { - > .mud-toggle-item:not(:last-child), > :not(:last-child) .mud-toggle-item { - &.mud-toggle-item-delimiter { - border-left-style: solid !important; + &.mud-toggle-group-horizontal { + &:not(.mud-toggle-group-rtl) { + > .mud-toggle-item:not(:first-child), > :not(:first-child) .mud-toggle-item { + &.mud-toggle-item-delimiter { + border-left-style: solid !important; + } + + margin-left: -1px; } + } + + &.mud-toggle-group-rtl { + > .mud-toggle-item:not(:last-child), > :not(:last-child) .mud-toggle-item { + &.mud-toggle-item-delimiter { + border-left-style: solid !important; + } - margin-left: -1px; + margin-left: -1px; + } } } -} -.mud-toggle-group-vertical { - > .mud-toggle-item:not(:first-child), > :not(:first-child) .mud-toggle-item { - &.mud-toggle-item-delimiter { - border-top-style: solid !important; - } + &.mud-toggle-group-vertical { + > .mud-toggle-item:not(:first-child), > :not(:first-child) .mud-toggle-item { + &.mud-toggle-item-delimiter { + border-top-style: solid !important; + } - margin-top: -1px; + margin-top: -1px; + } } } diff --git a/src/MudBlazor/TScripts/mudInput.js b/src/MudBlazor/TScripts/mudInput.js index 41e15831f5dc..be81eeeda402 100644 --- a/src/MudBlazor/TScripts/mudInput.js +++ b/src/MudBlazor/TScripts/mudInput.js @@ -9,6 +9,14 @@ class MudInput { input.value = ''; } } + + focusInput(elementId) { + const input = document.getElementById(elementId); + if (input && document.activeElement !== input) { + input.focus(); + input.click(); + } + } } window.mudInput = new MudInput(); diff --git a/src/MudBlazor/TScripts/mudPopover.js b/src/MudBlazor/TScripts/mudPopover.js index 7dcd982a2168..bf0343a55226 100644 --- a/src/MudBlazor/TScripts/mudPopover.js +++ b/src/MudBlazor/TScripts/mudPopover.js @@ -101,41 +101,14 @@ window.mudpopoverHelper = { // used to calculate the position of the popover calculatePopoverPosition: function (list, boundingRect, selfRect) { - let top = 0; - let left = 0; - if (list.indexOf('mud-popover-anchor-top-left') >= 0) { - left = boundingRect.left; - top = boundingRect.top; - } else if (list.indexOf('mud-popover-anchor-top-center') >= 0) { - left = boundingRect.left + boundingRect.width / 2; - top = boundingRect.top; - } else if (list.indexOf('mud-popover-anchor-top-right') >= 0) { - left = boundingRect.left + boundingRect.width; - top = boundingRect.top; - - } else if (list.indexOf('mud-popover-anchor-center-left') >= 0) { - left = boundingRect.left; - top = boundingRect.top + boundingRect.height / 2; - } else if (list.indexOf('mud-popover-anchor-center-center') >= 0) { - left = boundingRect.left + boundingRect.width / 2; - top = boundingRect.top + boundingRect.height / 2; - } else if (list.indexOf('mud-popover-anchor-center-right') >= 0) { - left = boundingRect.left + boundingRect.width; - top = boundingRect.top + boundingRect.height / 2; - - } else if (list.indexOf('mud-popover-anchor-bottom-left') >= 0) { - left = boundingRect.left; - top = boundingRect.top + boundingRect.height; - } else if (list.indexOf('mud-popover-anchor-bottom-center') >= 0) { - left = boundingRect.left + boundingRect.width / 2; - top = boundingRect.top + boundingRect.height; - } else if (list.indexOf('mud-popover-anchor-bottom-right') >= 0) { - left = boundingRect.left + boundingRect.width; - top = boundingRect.top + boundingRect.height; - } + let top = boundingRect.top; // default for mud-popover-anchor-top-left + let left = boundingRect.left; // default for mud-popover-anchor-top-left + + const isPositionOverride = list.indexOf('mud-popover-position-override') >= 0; let offsetX = 0; let offsetY = 0; + // transform origin if (list.indexOf('mud-popover-top-left') >= 0) { offsetX = 0; @@ -170,6 +143,39 @@ window.mudpopoverHelper = { offsetY = -selfRect.height; } + if (!isPositionOverride) { + // anchor origin, don't flip anchors on position override + if (list.indexOf('mud-popover-anchor-top-left') >= 0) { + left = boundingRect.left; + top = boundingRect.top; + } else if (list.indexOf('mud-popover-anchor-top-center') >= 0) { + left = boundingRect.left + boundingRect.width / 2; + top = boundingRect.top; + } else if (list.indexOf('mud-popover-anchor-top-right') >= 0) { + left = boundingRect.left + boundingRect.width; + top = boundingRect.top; + + } else if (list.indexOf('mud-popover-anchor-center-left') >= 0) { + left = boundingRect.left; + top = boundingRect.top + boundingRect.height / 2; + } else if (list.indexOf('mud-popover-anchor-center-center') >= 0) { + left = boundingRect.left + boundingRect.width / 2; + top = boundingRect.top + boundingRect.height / 2; + } else if (list.indexOf('mud-popover-anchor-center-right') >= 0) { + left = boundingRect.left + boundingRect.width; + top = boundingRect.top + boundingRect.height / 2; + + } else if (list.indexOf('mud-popover-anchor-bottom-left') >= 0) { + left = boundingRect.left; + top = boundingRect.top + boundingRect.height; + } else if (list.indexOf('mud-popover-anchor-bottom-center') >= 0) { + left = boundingRect.left + boundingRect.width / 2; + top = boundingRect.top + boundingRect.height; + } else if (list.indexOf('mud-popover-anchor-bottom-right') >= 0) { + left = boundingRect.left + boundingRect.width; + top = boundingRect.top + boundingRect.height; + } + } return { top: top, left: left, offsetX: offsetX, offsetY: offsetY, anchorY: top, anchorX: left }; @@ -271,6 +277,23 @@ window.mudpopoverHelper = { const zIndexAuto = popoverNodeStyle.getPropertyValue('z-index') === 'auto'; const classListArray = Array.from(classList); + if (isPositionOverride) { + const positiontop = parseInt(popoverContentNode.getAttribute('data-pc-y')) || boundingRect.top; + const positionleft = parseInt(popoverContentNode.getAttribute('data-pc-x')) || boundingRect.left; + const scrollLeft = window.scrollX; + const scrollTop = window.scrollY; + + // bounding rect for flipping + boundingRect = { + left: positionleft - scrollLeft, + top: positiontop - scrollTop, + right: positionleft + 1, + bottom: positiontop + 1, + width: 1, + height: 1 + }; + } + // calculate position based on opening anchor/transform const position = window.mudpopoverHelper.calculatePopoverPosition(classListArray, boundingRect, selfRect); let left = position.left; // X-coordinate of the popover @@ -296,23 +319,6 @@ window.mudpopoverHelper = { popoverContentNode.mudHeight = null; } - // get the top/left/ from popoverContentNode if the popover has been hardcoded for position - if (isPositionOverride) { - left = parseInt(popoverContentNode.style['left']) || left; - top = parseInt(popoverContentNode.style['top']) || top; - // no offset when hardcoded - offsetX = 0; - offsetY = 0; - // bounding rect for flipping - boundingRect = { - left: left, - top: top, - right: left + selfRect.width, - bottom: top + selfRect.height, - width: selfRect.width, - height: selfRect.height - }; - } // flipping logic if (isFlipOnOpen || isFlipAlways) { @@ -575,12 +581,6 @@ window.mudpopoverHelper = { offsetY += window.scrollY } - if (isPositionOverride) { - // no offset if popover position is hardcoded - offsetX = 0; - offsetY = 0; - } - popoverContentNode.style['left'] = (left + offsetX) + 'px'; popoverContentNode.style['top'] = (top + offsetY) + 'px'; diff --git a/src/MudBlazor/Themes/Models/Breakpoints.cs b/src/MudBlazor/Themes/Models/Breakpoints.cs index 0890c8ec2b96..9287c8a249f9 100644 --- a/src/MudBlazor/Themes/Models/Breakpoints.cs +++ b/src/MudBlazor/Themes/Models/Breakpoints.cs @@ -13,38 +13,38 @@ public class Breakpoints // ReSharper disable InconsistentNaming /// - /// Gets or sets the breakpoint value for extra small screens (xs). - /// Default value is "0px". + /// The breakpoint value for extra small screens (xs). + /// Defaults to 0px. /// public string xs { get; set; } = "0px"; /// - /// Gets or sets the breakpoint value for small screens (sm). - /// Default value is "600px". + /// The breakpoint value for small screens (sm). + /// Defaults to 600px. /// public string sm { get; set; } = "600px"; /// - /// Gets or sets the breakpoint value for medium screens (md). - /// Default value is "960px". + /// The breakpoint value for medium screens (md). + /// Defaults to 960px. /// public string md { get; set; } = "960px"; /// - /// Gets or sets the breakpoint value for large screens (lg). - /// Default value is "1280px". + /// The breakpoint value for large screens (lg). + /// Defaults to 1280px. /// public string lg { get; set; } = "1280px"; /// - /// Gets or sets the breakpoint value for extra large screens (xl). - /// Default value is "1920px". + /// The breakpoint value for extra large screens (xl). + /// Defaults to 1920px. /// public string xl { get; set; } = "1920px"; /// - /// Gets or sets the breakpoint value for extra extra large screens (xxl). - /// Default value is "2560px". + /// The breakpoint value for extra extra large screens (xxl). + /// Defaults to 2560px. /// public string xxl { get; set; } = "2560px"; diff --git a/src/MudBlazor/Themes/Models/LayoutProperties.cs b/src/MudBlazor/Themes/Models/LayoutProperties.cs index c282a0ee52a4..0182ad50deea 100644 --- a/src/MudBlazor/Themes/Models/LayoutProperties.cs +++ b/src/MudBlazor/Themes/Models/LayoutProperties.cs @@ -7,32 +7,32 @@ public class LayoutProperties { /// - /// Gets or sets the default border radius. + /// The default border radius. /// public string DefaultBorderRadius { get; set; } = "4px"; /// - /// Gets or sets the width of the mini drawer on the left side. + /// The width of the mini drawer on the left side. /// public string DrawerMiniWidthLeft { get; set; } = "56px"; /// - /// Gets or sets the width of the mini drawer on the right side. + /// The width of the mini drawer on the right side. /// public string DrawerMiniWidthRight { get; set; } = "56px"; /// - /// Gets or sets the width of the drawer on the left side. + /// The width of the drawer on the left side. /// public string DrawerWidthLeft { get; set; } = "240px"; /// - /// Gets or sets the width of the drawer on the right side. + /// The width of the drawer on the right side. /// public string DrawerWidthRight { get; set; } = "240px"; /// - /// Gets or sets the height of the appbar. + /// The height of the appbar. /// public string AppbarHeight { get; set; } = "64px"; } diff --git a/src/MudBlazor/Themes/Models/Shadow.cs b/src/MudBlazor/Themes/Models/Shadow.cs index de0568b40c0f..7d81a100c062 100644 --- a/src/MudBlazor/Themes/Models/Shadow.cs +++ b/src/MudBlazor/Themes/Models/Shadow.cs @@ -7,7 +7,7 @@ public class Shadow { /// - /// Gets or sets the elevation levels for the shadow. + /// The elevation levels for the shadow. /// public string[] Elevation { get; set; } = { diff --git a/src/MudBlazor/Themes/Models/Typography.cs b/src/MudBlazor/Themes/Models/Typography.cs index b2c27045b1ba..5c8c0ab2f066 100644 --- a/src/MudBlazor/Themes/Models/Typography.cs +++ b/src/MudBlazor/Themes/Models/Typography.cs @@ -9,7 +9,7 @@ namespace MudBlazor public class Typography { /// - /// Gets or sets the typography settings for the default typo. + /// The typography settings for the default typo. /// /// /// Defaults to the values from the constructor. @@ -17,7 +17,7 @@ public class Typography public BaseTypography Default { get; set; } = new DefaultTypography(); /// - /// Gets or sets the typography settings for . + /// The typography settings for . /// /// /// Defaults to the values from the constructor. @@ -25,7 +25,7 @@ public class Typography public BaseTypography H1 { get; set; } = new H1Typography(); /// - /// Gets or sets the typography settings for . + /// The typography settings for . /// /// /// Defaults to the values from the constructor. @@ -33,7 +33,7 @@ public class Typography public BaseTypography H2 { get; set; } = new H2Typography(); /// - /// Gets or sets the typography settings for . + /// The typography settings for . /// /// /// Defaults to the values from the constructor. @@ -41,7 +41,7 @@ public class Typography public BaseTypography H3 { get; set; } = new H3Typography(); /// - /// Gets or sets the typography settings for . + /// The typography settings for . /// /// /// Defaults to the values from the constructor. @@ -49,7 +49,7 @@ public class Typography public BaseTypography H4 { get; set; } = new H4Typography(); /// - /// Gets or sets the typography settings for . + /// The typography settings for . /// /// /// Defaults to the values from the constructor. @@ -57,7 +57,7 @@ public class Typography public BaseTypography H5 { get; set; } = new H5Typography(); /// - /// Gets or sets the typography settings for . + /// The typography settings for . /// /// /// Defaults to the values from the constructor. @@ -65,7 +65,7 @@ public class Typography public BaseTypography H6 { get; set; } = new H6Typography(); /// - /// Gets or sets the typography settings for . + /// The typography settings for . /// /// /// Defaults to the values from the constructor. @@ -73,7 +73,7 @@ public class Typography public BaseTypography Subtitle1 { get; set; } = new Subtitle1Typography(); /// - /// Gets or sets the typography settings for . + /// The typography settings for . /// /// /// Defaults to the values from the constructor. @@ -81,7 +81,7 @@ public class Typography public BaseTypography Subtitle2 { get; set; } = new Subtitle2Typography(); /// - /// Gets or sets the typography settings for . + /// The typography settings for . /// /// /// Defaults to the values from the constructor. @@ -89,7 +89,7 @@ public class Typography public BaseTypography Body1 { get; set; } = new Body1Typography(); /// - /// Gets or sets the typography settings for . + /// The typography settings for . /// /// /// Defaults to the values from the constructor. @@ -97,7 +97,7 @@ public class Typography public BaseTypography Body2 { get; set; } = new Body2Typography(); /// - /// Gets or sets the typography settings for . + /// The typography settings for . /// /// /// Defaults to the values from the constructor. @@ -105,7 +105,7 @@ public class Typography public BaseTypography Button { get; set; } = new ButtonTypography(); /// - /// Gets or sets the typography settings for . + /// The typography settings for . /// /// /// Defaults to the values from the constructor. @@ -113,7 +113,7 @@ public class Typography public BaseTypography Caption { get; set; } = new CaptionTypography(); /// - /// Gets or sets the typography settings for . + /// The typography settings for . /// /// /// Defaults to the values from the constructor. @@ -127,7 +127,7 @@ public class Typography public class DefaultTypography : BaseTypography { /// - /// Initializes a new instance of the class with default values. + /// Initializes the class with default values. /// public DefaultTypography() { @@ -145,7 +145,7 @@ public DefaultTypography() public class H1Typography : BaseTypography { /// - /// Initializes a new instance of the class with default values. + /// Initializes the class with default values. /// public H1Typography() { @@ -162,7 +162,7 @@ public H1Typography() public class H2Typography : BaseTypography { /// - /// Initializes a new instance of the class with default values. + /// Initializes the class with default values. /// public H2Typography() { @@ -179,7 +179,7 @@ public H2Typography() public class H3Typography : BaseTypography { /// - /// Initializes a new instance of the class with default values. + /// Initializes the class with default values. /// public H3Typography() { @@ -196,7 +196,7 @@ public H3Typography() public class H4Typography : BaseTypography { /// - /// Initializes a new instance of the class with default values. + /// Initializes the class with default values. /// public H4Typography() { @@ -213,7 +213,7 @@ public H4Typography() public class H5Typography : BaseTypography { /// - /// Initializes a new instance of the class with default values. + /// Initializes the class with default values. /// public H5Typography() { @@ -230,7 +230,7 @@ public H5Typography() public class H6Typography : BaseTypography { /// - /// Initializes a new instance of the class with default values. + /// Initializes the class with default values. /// public H6Typography() { @@ -247,7 +247,7 @@ public H6Typography() public class Subtitle1Typography : BaseTypography { /// - /// Initializes a new instance of the class with default values. + /// Initializes the class with default values. /// public Subtitle1Typography() { @@ -264,7 +264,7 @@ public Subtitle1Typography() public class Subtitle2Typography : BaseTypography { /// - /// Initializes a new instance of the class with default values. + /// Initializes the class with default values. /// public Subtitle2Typography() { @@ -281,7 +281,7 @@ public Subtitle2Typography() public class Body1Typography : BaseTypography { /// - /// Initializes a new instance of the class with default values. + /// Initializes the class with default values. /// public Body1Typography() { @@ -298,7 +298,7 @@ public Body1Typography() public class Body2Typography : BaseTypography { /// - /// Initializes a new instance of the class with default values. + /// Initializes the class with default values. /// public Body2Typography() { @@ -315,7 +315,7 @@ public Body2Typography() public class ButtonTypography : BaseTypography { /// - /// Initializes a new instance of the class with default values. + /// Initializes the class with default values. /// public ButtonTypography() { @@ -333,7 +333,7 @@ public ButtonTypography() public class CaptionTypography : BaseTypography { /// - /// Initializes a new instance of the class with default values. + /// Initializes the class with default values. /// public CaptionTypography() { @@ -350,7 +350,7 @@ public CaptionTypography() public class OverlineTypography : BaseTypography { /// - /// Initializes a new instance of the class with default values. + /// Initializes the class with default values. /// public OverlineTypography() { @@ -381,32 +381,32 @@ public OverlineTypography() public abstract class BaseTypography { /// - /// Gets or sets the font family. + /// The font family. /// public string[]? FontFamily { get; set; } /// - /// Gets or sets the font weight. + /// The font weight. /// public string? FontWeight { get; set; } /// - /// Gets or sets the font size. + /// The font size. /// public string? FontSize { get; set; } /// - /// Gets or sets the line height. + /// The line height. /// public string? LineHeight { get; set; } /// - /// Gets or sets the letter spacing. + /// The letter spacing. /// public string? LetterSpacing { get; set; } /// - /// Gets or sets the text transform. + /// The text transformation. /// public string TextTransform { get; set; } = "none"; } diff --git a/src/MudBlazor/Themes/Models/Z-Index.cs b/src/MudBlazor/Themes/Models/Z-Index.cs index 441d7581083e..eb453e775ecf 100644 --- a/src/MudBlazor/Themes/Models/Z-Index.cs +++ b/src/MudBlazor/Themes/Models/Z-Index.cs @@ -7,38 +7,38 @@ public class ZIndex { /// - /// Gets or sets the Z-index value for the Drawer component. - /// Default value is "1100"". + /// The Z-index value for the Drawer component. + /// Defaults to 1100.. /// public int Drawer { get; set; } = 1100; /// - /// Gets or sets the Z-index value for the Popover component. - /// Default value is "1200". + /// The Z-index value for the Popover component. + /// Defaults to 1200. /// public int Popover { get; set; } = 1200; /// - /// Gets or sets the Z-index value for the AppBar component. - /// Default value is "1300". + /// The Z-index value for the AppBar component. + /// Defaults to 1300. /// public int AppBar { get; set; } = 1300; /// - /// Gets or sets the Z-index value for the Dialog component. - /// Default value is "1400". + /// The Z-index value for the Dialog component. + /// Defaults to 1400. /// public int Dialog { get; set; } = 1400; /// - /// Gets or sets the Z-index value for the SnackBar component. - /// Default value is "1500". + /// The Z-index value for the SnackBar component. + /// Defaults to 1500. /// public int Snackbar { get; set; } = 1500; /// - /// Gets or sets the Z-index value for the Tooltip component. - /// Default value is "1600". + /// The Z-index value for the Tooltip component. + /// Defaults to 1600. /// public int Tooltip { get; set; } = 1600; } diff --git a/src/MudBlazor/Themes/MudTheme.cs b/src/MudBlazor/Themes/MudTheme.cs index c5b5eb6322ee..9e923049f369 100644 --- a/src/MudBlazor/Themes/MudTheme.cs +++ b/src/MudBlazor/Themes/MudTheme.cs @@ -7,43 +7,43 @@ public class MudTheme { /// - /// Gets or sets the palette for the light theme. + /// The palette for the light theme. /// /// Renamed from Palette to PaletteLight in v7. public PaletteLight PaletteLight { get; set; } /// - /// Gets or sets the palette for the dark theme. + /// The palette for the dark theme. /// public PaletteDark PaletteDark { get; set; } /// - /// Gets or sets the shadow settings. + /// The shadow settings. /// public Shadow Shadows { get; set; } /// - /// Gets or sets the typography settings. + /// The typography settings. /// public Typography Typography { get; set; } /// - /// Gets or sets the layout properties. + /// The layout properties. /// public LayoutProperties LayoutProperties { get; set; } /// - /// Gets or sets the z-index values. + /// The z-index values. /// public ZIndex ZIndex { get; set; } /// - /// Gets or sets the pseudo CSS styles. + /// The pseudo CSS styles. /// public PseudoCss PseudoCss { get; set; } /// - /// Initializes a new instance of the class. + /// Initializes the class. /// public MudTheme() { diff --git a/src/MudBlazor/Utilities/StringHelpers.cs b/src/MudBlazor/Utilities/StringHelpers.cs index 2d8451ee405c..6f7ad0fc4f79 100644 --- a/src/MudBlazor/Utilities/StringHelpers.cs +++ b/src/MudBlazor/Utilities/StringHelpers.cs @@ -3,7 +3,7 @@ namespace MudBlazor.Utilities; #nullable enable -internal static class StringHelpers +internal static partial class StringHelpers { /// /// Converts a double value to its string representation, rounded to 4 decimal places.