diff --git a/examples/Demo/Shared/Components/ApiDocumentation.razor b/examples/Demo/Shared/Components/ApiDocumentation.razor index f27addbad..11c615da7 100644 --- a/examples/Demo/Shared/Components/ApiDocumentation.razor +++ b/examples/Demo/Shared/Components/ApiDocumentation.razor @@ -26,7 +26,7 @@ string header = Properties.Any(x => x.IsParameter) ? "Parameters" : "Properties";

@header

- + @context.Name @context.Type @@ -82,7 +82,7 @@ {

EventCallbacks

- + @context.Name @@ -98,7 +98,7 @@ {

Methods

- + @context.Name @foreach (var param in @context.Parameters) diff --git a/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml b/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml index aad50f47d..e1407fac8 100644 --- a/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml +++ b/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml @@ -1183,6 +1183,11 @@ This is rendered automatically if is not used. + + + Gets or sets the index (1-based) of the column + + Gets or sets the an optional CSS class name. @@ -1858,6 +1863,14 @@ is small or if you are using pagination. + + + This is applicable only when using . It defines how many additional items will be rendered + before and after the visible region to reduce rendering frequency during scrolling. While higher values can improve + scroll smoothness by rendering more items off-screen, they can also increase initial load times. Finding a balance + based on your data set size and user experience requirements is recommended. The default value is 3. + + This is applicable only when using . It defines an expected height in pixels for @@ -2002,6 +2015,16 @@ Sets to automatically fit the columns to the available width as best it can. + + + Gets or sets the size of each row in the grid based on the enum. + + + + + Gets or sets a value indicating whether the grid should allow multiple lines of text in cells. + + Gets or sets a value indicating whether the grid should save its paging state in the URL. @@ -2013,6 +2036,11 @@ Only relevant when is set to on multiple grids on a single page. + + + Gets or sets a value indicating whether the grids' first cell should be focused. + + Constructs an instance of . @@ -2168,7 +2196,7 @@ Gets or sets the owning component. - + Gets or sets the owning component @@ -2178,6 +2206,11 @@ Gets a reference to the column that this cell belongs to. + + + Gets a reference to the enclosing . + + @@ -2207,11 +2240,16 @@ Gets or sets the content to be rendered inside the component. - + Gets or sets the owning component + + + Gets a reference to the enclosing . + + @@ -13430,6 +13468,32 @@ Resize datagrid columns by exact pixel values + + + The height of each in a . + Values are in pixels. + + + + + Small row height (default) + + + + + Medium row height + + + + + Smaller row height + + + + + Large row height + + The type of in a . diff --git a/examples/Demo/Shared/Pages/DataGrid/DataGridPage.razor b/examples/Demo/Shared/Pages/DataGrid/DataGridPage.razor index a0c6b2f6c..f0c350d08 100644 --- a/examples/Demo/Shared/Pages/DataGrid/DataGridPage.razor +++ b/examples/Demo/Shared/Pages/DataGrid/DataGridPage.razor @@ -289,7 +289,7 @@ fluent-data-grid-row:has([row-selected]) { - Example of using the Class parameter to style parts of the grid. Note that the class used in the example (multiline-text) has been added to the FluentDataGridCell css. + Set the grid parameter MultiLine to true when you have cells in your data that will take up more than a single line. diff --git a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridAutoFit.razor b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridAutoFit.razor index c3bf9a049..2ff7d08f3 100644 --- a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridAutoFit.razor +++ b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridAutoFit.razor @@ -3,7 +3,7 @@

With auto-fit

- + diff --git a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridCustomComparer.razor b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridCustomComparer.razor index 5202ff2f1..e0df24e86 100644 --- a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridCustomComparer.razor +++ b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridCustomComparer.razor @@ -10,51 +10,52 @@ } + - - None - Discrete - Exact - - - + + None + Discrete + Exact + + + - - - Flag of @(context.Code) - - - - - - - @* - - diff --git a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridMultilineText.razor b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridMultilineText.razor index 89dcc9285..bf68608e0 100644 --- a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridMultilineText.razor +++ b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridMultilineText.razor @@ -1,7 +1,7 @@ - + - + @code { @@ -13,4 +13,4 @@ new Person(10944, "António Langa", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."), new Person(11203, "Julie Smith","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."), }.AsQueryable(); -} \ No newline at end of file +} diff --git a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridNotVirtualizedLoadingAndEmpty.razor b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridNotVirtualizedLoadingAndEmpty.razor index 0af35295a..ffe355d69 100644 --- a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridNotVirtualizedLoadingAndEmpty.razor +++ b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridNotVirtualizedLoadingAndEmpty.razor @@ -1,5 +1,5 @@ 
- + @@ -53,7 +53,7 @@ grid?.SetLoadingState(true); items = null; - await Task.Delay(2500); + await Task.Delay(1500); items = GenerateSampleGridData(100); grid?.SetLoadingState(false); diff --git a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridRemoteData.razor b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridRemoteData.razor index a5943a326..8f49828f2 100644 --- a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridRemoteData.razor +++ b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridRemoteData.razor @@ -4,7 +4,14 @@ @inject NavigationManager NavManager
- + diff --git a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridTableScrollbars.razor b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridTableScrollbars.razor index 13fe3ab17..0958218f1 100644 --- a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridTableScrollbars.razor +++ b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridTableScrollbars.razor @@ -3,7 +3,7 @@ - +
diff --git a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridTemplateColumns.razor b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridTemplateColumns.razor index 5496035fe..c1e5d93a7 100644 --- a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridTemplateColumns.razor +++ b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridTemplateColumns.razor @@ -1,6 +1,6 @@ @inject DataSource Data - +
Flag of @(context.CountryCode) @@ -28,4 +28,4 @@ void Bonus(Person p) => message = $"You want to give {p.FirstName} {p.LastName} a regular bonus"; void DoubleBonus(Person p) => message = $"You want to give {p.FirstName} {p.LastName} a double bonus"; -} \ No newline at end of file +} diff --git a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridTemplateColumns2.razor b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridTemplateColumns2.razor index f9b8bcce8..bdc517129 100644 --- a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridTemplateColumns2.razor +++ b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridTemplateColumns2.razor @@ -1,5 +1,11 @@  - + @@ -14,7 +20,7 @@ { public string Name { get; set; } public int Age { get; set; } - + public SampleGridData(string name, int age) { Name = name; @@ -30,12 +36,12 @@ new SampleGridData("Bob", 20 ) }; - private void HandleRowFocus(FluentDataGridRow row) + private void HandleRowClick(FluentDataGridRow row) { DemoLogger.WriteLine($"Row focused: {row.RowIndex}"); } - private void HandleCellFocus(FluentDataGridCell cell) + private void HandleCellClick(FluentDataGridCell cell) { DemoLogger.WriteLine($"Cell focused: {cell.GridColumn}"); } diff --git a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridTypical.razor b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridTypical.razor index 43fd95f81..41394e209 100644 --- a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridTypical.razor +++ b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridTypical.razor @@ -1,55 +1,54 @@ - -@inject DataSource Data +@inject DataSource Data

To test set ResizeType on the DataGrid to either DataGridResizeType.Discrete or DataGridResizeType.Exact

Remove the parameter completely to get the original behavior

- - - Flag of @(context.Code) - - - - - - - - - - - -
- - 0 - 50 - 100 - 150 - -

- - 0 - 50 - 100 - 150 - -
-
-
-
- +
+ + + Flag of @(context.Code) + + + + + + + + + + + +
+ + 0 + 50 + 100 + 150 + +

+ + 0 + 50 + 100 + 150 + +
+
+
+
+
@@ -74,7 +73,7 @@ .ThenDescending(x => x.Medals.Silver) .ThenDescending(x => x.Medals.Bronze); - Func rowClass = x => x.Name.StartsWith("A") ? "highlighted-row" : null; + Func rowClass = x => x.Name.StartsWith("A") ? "highlighted" : null; Func rowStyle = x => x.Name.StartsWith("Au") ? "background-color: var(--highlight-bg)" : null; //IQueryable? FilteredItems => items?.Where(x => x.Name.Contains(nameFilter, StringComparison.CurrentCultureIgnoreCase)); diff --git a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridTypical.razor.css b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridTypical.razor.css index 2aa0928d6..49724e23d 100644 --- a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridTypical.razor.css +++ b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridTypical.razor.css @@ -1,7 +1,7 @@ /* Ensure all the flags are the same size, and centered */ .flag { height: 1rem; - margin: auto; + margin-top: 7px; border: 1px solid var(--neutral-layer-3); } .search-box { diff --git a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridVirtualize.razor b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridVirtualize.razor index 6277b5307..4e2b26ba8 100644 --- a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridVirtualize.razor +++ b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridVirtualize.razor @@ -1,10 +1,10 @@ -
- +
+ - - - - + + + +   Nothing to see here. Carry on! @@ -70,7 +70,7 @@ items = null; grid?.SetLoadingState(true); - await Task.Delay(2500); + await Task.Delay(1500); items = GenerateSampleGridData(5000); } diff --git a/examples/Demo/Shared/Pages/Tabs/Examples/TabsDynamic.razor b/examples/Demo/Shared/Pages/Tabs/Examples/TabsDynamic.razor index db13cc8ac..fa25b58cb 100644 --- a/examples/Demo/Shared/Pages/Tabs/Examples/TabsDynamic.razor +++ b/examples/Demo/Shared/Pages/Tabs/Examples/TabsDynamic.razor @@ -22,6 +22,7 @@ OptionValue="@(i => $"tab-{i.Index}")" @bind-Value="@SelectedTabId" /> Add and select + @code { List Items = new List(DataSource.AllMonths); diff --git a/examples/Demo/Shared/SampleData/DataSource.cs b/examples/Demo/Shared/SampleData/DataSource.cs index 078c6a973..741c3850e 100644 --- a/examples/Demo/Shared/SampleData/DataSource.cs +++ b/examples/Demo/Shared/SampleData/DataSource.cs @@ -295,7 +295,7 @@ private static string GetMonthName(int index) new Country("tr", "Turkey", new Medals { Gold = 0, Silver = 3, Bronze = 5}), new Country("ug", "Uganda", new Medals { Gold = 1, Silver = 1, Bronze = 0}), new Country("ua", "Ukraine", new Medals { Gold = 3, Silver = 5, Bronze = 4}), - new Country("us", "United States", new Medals { Gold = 40, Silver = 44, Bronze = 42}), + new Country("us", "United States of America", new Medals { Gold = 40, Silver = 44, Bronze = 42}), new Country("uz", "Uzbekistan", new Medals { Gold = 8, Silver = 2, Bronze = 3}), new Country("zm", "Zambia", new Medals { Gold = 0, Silver = 0, Bronze = 1}) diff --git a/examples/Demo/Shared/wwwroot/css/site.css b/examples/Demo/Shared/wwwroot/css/site.css index 15feed298..3655ed8a5 100644 --- a/examples/Demo/Shared/wwwroot/css/site.css +++ b/examples/Demo/Shared/wwwroot/css/site.css @@ -203,11 +203,11 @@ code { padding: 5px; } -.highlighted-row { +.highlighted { background-color: var(--accent-stroke-control-active); } - .highlighted-row > fluent-data-grid-cell { + .highlighted > td { color: var(--neutral-fill-inverse-rest); } diff --git a/src/Core.Assets/src/index.ts b/src/Core.Assets/src/index.ts index 4db995bda..b53833164 100644 --- a/src/Core.Assets/src/index.ts +++ b/src/Core.Assets/src/index.ts @@ -268,24 +268,6 @@ export function afterStarted(blazor: Blazor, mode: string) { } }); - blazor.registerCustomEventType('cellfocus', { - browserEventName: 'cell-focused', - createEventArgs: event => { - return { - cellId: event.detail.attributes['cell-id'].value - }; - } - }); - - blazor.registerCustomEventType('rowfocus', { - browserEventName: 'row-focused', - createEventArgs: event => { - return { - rowId: event.detail.attributes['row-id'].value - }; - } - }); - blazor.registerCustomEventType('splitterresized', { browserEventName: 'splitterresized', createEventArgs: event => { diff --git a/src/Core/Components/DataGrid/Columns/ColumnBase.razor b/src/Core/Components/DataGrid/Columns/ColumnBase.razor index a9e18748b..469d4f218 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnBase.razor +++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor @@ -17,149 +17,129 @@ { string? tooltip = Tooltip ? (HeaderTooltip ?? Title) : null; - - - + + + @if (AnyColumnActionEnabled) + { +
@Title
- @if (Grid.SortByAscending.HasValue && ShowSortIcon) + @if (Grid.SortByAscending.HasValue && IsActiveSortColumn) { if (Grid.SortByAscending == true) { - + } else { - + } } - @if (Grid.ResizeType is not null && ColumnOptions is not null) + @if (ColumnOptions is not null && Filtered.GetValueOrDefault()) { - @if (Filtered.GetValueOrDefault()) - { - - } + }
- - @if (Sortable.HasValue ? Sortable.Value : IsSortableByDefault()) - { - - @GetSortOptionText() - - } - @if (Grid.ResizeType is not null && Grid.ResizableColumns) - { - @Grid.ColumnResizeLabels.ResizeMenu - } - @if (ColumnOptions is not null) - { - @Grid.ColumnOptionsLabels.OptionsMenu - } - -
+ } + else + { +
+
@Title
+
+ } + + @if (Sortable.HasValue ? Sortable.Value : IsSortableByDefault()) + { + + @GetSortOptionText() + + } + @if (Grid.ResizeType is not null && Grid.ResizableColumns) + { + @Grid.ColumnResizeLabels.ResizeMenu + } + @if (ColumnOptions is not null) + { + @Grid.ColumnOptionsLabels.OptionsMenu + } +
} else { string? tooltip = Tooltip ? (HeaderTooltip ?? Title) : null; + string? wdelta = "20px"; + + if (Grid.ResizeType is not null || ColumnOptions is not null) + { + wdelta = "56px"; + } @if (Align == Align.Start || Align == Align.Center) { @if (Grid.ResizeType is not null) { - - - + @OptionsButton() } else { @if (ColumnOptions is not null) { - - @if (Filtered.GetValueOrDefault()) - { - - } - else - { - - } - - + @FilterButton() } } } - if (Sortable.HasValue ? Sortable.Value : IsSortableByDefault()) + @if (Sortable.HasValue ? Sortable.Value : IsSortableByDefault()) { - - - + +
@Title
- @if (Grid.SortByAscending.HasValue && ShowSortIcon) + @if (Grid.SortByAscending.HasValue && IsActiveSortColumn) { if (Grid.SortByAscending == true) { - + } else { - + } } - @if (Grid.ResizeType is not null && ColumnOptions is not null) - { - @if (Filtered.GetValueOrDefault()) + @if (ColumnOptions is not null && Filtered.GetValueOrDefault()) { - + } - }
-
} else { -
+
@Title
- @if (Grid.ResizeType is not null && ColumnOptions is not null) - { - @if (Filtered.GetValueOrDefault()) - { - - } - }
+ @if (ColumnOptions is not null && Filtered.GetValueOrDefault()) + { + + } } @if (Align == Align.End) { @if (Grid.ResizeType is not null) { - - - + @OptionsButton() } else { @if (ColumnOptions is not null) { - - @if (Filtered.GetValueOrDefault()) - { - - } - else - { - - } - - + @FilterButton() } } } + } } @@ -171,4 +151,28 @@ @PlaceholderTemplate(placeholderContext) } } + + private RenderFragment OptionsButton() + { + return + @ + + ; + } + + private RenderFragment FilterButton() + { + return + @ + @if (Filtered.GetValueOrDefault()) + { + + } + else + { + + } + + ; + } } diff --git a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs index def61a9c7..d0a3a3299 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs @@ -14,7 +14,7 @@ public abstract partial class ColumnBase { private bool _isMenuOpen; private static readonly string[] KEYBOARD_MENU_SELECT_KEYS = ["Enter", "NumpadEnter"]; - private readonly string _columnId = $"column-header{Identifier.NewId()}"; + private readonly string _columnId = Identifier.NewId(); [CascadingParameter] internal InternalGridContext InternalGridContext { get; set; } = default!; @@ -26,6 +26,13 @@ public abstract partial class ColumnBase [Parameter] public string? Title { get; set; } + + /// + /// Gets or sets the index (1-based) of the column + /// + [Parameter] + public int Index { get; set; } + /// /// Gets or sets the an optional CSS class name. /// If specified, this is included in the class attribute of header and grid cells @@ -140,6 +147,14 @@ public abstract partial class ColumnBase protected bool AnyColumnActionEnabled => Sortable is true || IsDefaultSortColumn || ColumnOptions != null || Grid.ResizableColumns; + protected override void OnInitialized() + { + if (GetType() == typeof(SelectColumn)) + { + Align = Align.Center; + } + } + /// /// Event callback for when the row is clicked. /// @@ -221,7 +236,7 @@ protected void HandleKeyDown(FluentKeyCodeEventArgs e) } } - public bool ShowSortIcon; + public bool IsActiveSortColumn; /// /// Constructs an instance of . @@ -252,7 +267,7 @@ private async Task HandleSortMenuKeyDownAsync(KeyboardEventArgs args) if (KEYBOARD_MENU_SELECT_KEYS.Contains(args.Key)) { await Grid.SortByColumnAsync(this); - StateHasChanged(); + StateHasChanged(); _isMenuOpen = false; } } @@ -277,12 +292,12 @@ private async Task HandleOptionsMenuKeyDownAsync(KeyboardEventArgs args) private string GetSortOptionText() { - if (Grid.SortByAscending.HasValue && ShowSortIcon) + if (Grid.SortByAscending.HasValue && IsActiveSortColumn) { if (Grid.SortByAscending is true) { return Grid.ColumnSortLabels.SortMenuAscendingLabel; - } + } else { return Grid.ColumnSortLabels.SortMenuDescendingLabel; diff --git a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.css b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.css index 632ba1fbd..cbf496e9c 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.css +++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.css @@ -1,30 +1,16 @@ -/* Contains the title text and sort indicator, and expands to fill as much of the col width as it can */ .col-title { - display: flex; /* So that we can make col-title-text expand as much as possible, and still hide overflow with ellipsis */ - min-width: 0px; - flex-grow: 1; - padding: 0; + padding: 0.4rem 0.8rem; + user-select: none; } -/* If the column is sortable, its title is rendered as a button element for accessibility and to support navigation by tab */ -button.col-title { - border: none; - background: none; - position: relative; - cursor: pointer; -} - -.col-justify-center .col-title { - justify-content: center; -} - -.col-justify-end .col-title, .col-justify-right .col-title { - flex-direction: row-reverse; /* For end-justified cols, the sort indicator should appear before the title text */ -} - -/* We put the column title text in its own element primarily so that it can use text-overflow: ellipsis */ .col-title-text { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; + font-weight: 600; +} + +th.col-justify-center svg { + align-content: center; + text-align:center; } diff --git a/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor b/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor index 23d1aed46..120a15234 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor +++ b/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor @@ -24,6 +24,7 @@ ImmediateDelay="200" Label="@Grid.ColumnResizeLabels.ExactLabel" AutoComplete="off" + Autofocus="true" @onkeydown="@HandleColumnWidthKeyDownAsync" />
diff --git a/src/Core/Components/DataGrid/Columns/SelectColumn.cs b/src/Core/Components/DataGrid/Columns/SelectColumn.cs index 5eaa739eb..f8cc70cfd 100644 --- a/src/Core/Components/DataGrid/Columns/SelectColumn.cs +++ b/src/Core/Components/DataGrid/Columns/SelectColumn.cs @@ -426,8 +426,7 @@ private RenderFragment GetHeaderContent() builder.AddAttribute(3, "OnClick", EventCallback.Factory.Create(this, OnClickAllAsync)); builder.AddAttribute(4, "onkeydown", EventCallback.Factory.Create(this, OnKeyAllAsync)); } - builder.AddAttribute(5, "Style", "margin-left: 12px;"); - builder.AddAttribute(6, "Title", iconAllChecked == IconIndeterminate + builder.AddAttribute(5, "Title", iconAllChecked == IconIndeterminate ? TitleAllIndeterminate : (iconAllChecked == GetIcon(true) ? TitleAllChecked : TitleAllUnchecked)); builder.CloseComponent(); diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor b/src/Core/Components/DataGrid/FluentDataGrid.razor index 074484ca4..84f90548f 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor @@ -17,60 +17,62 @@ } - + @if (GenerateHeader != GenerateHeaderOption.None) { DataGridRowType headerType = DataGridRowType.Header; if (GenerateHeader == GenerateHeaderOption.Sticky) + { headerType = DataGridRowType.StickyHeader; - - @_renderColumnHeaders - - } - - @if (Loading) - { - @_renderLoadingContent + } + + + @_renderColumnHeaders + + } - else - { - @if (Virtualize) + + @if (Loading) { - if (_internalGridContext.TotalItemCount == 0) + @_renderLoadingContent + } + else + { + @if (Virtualize) { - @_renderEmptyContent + if (_internalGridContext.TotalItemCount == 0) + { + @_renderEmptyContent + } + else + { + + } } else { - + @_renderNonVirtualizedRows } } - else + @if (_manualGrid) { - @_renderNonVirtualizedRows + @ChildContent } - } - @if (_manualGrid) - { - @ChildContent - } - + +
@@ -96,14 +98,15 @@ { var rowClass = RowClass?.Invoke(item) ?? null; var rowStyle = RowStyle?.Invoke(item) ?? null; - + + @for (var colIndex = 0; colIndex < _columns.Count; colIndex++) { var col = _columns[colIndex]; string? tooltip = col.Tooltip ? @col.RawCellContent(item) : null; - + @((RenderFragment)(__builder => col.CellContent(__builder, item))) } @@ -114,12 +117,12 @@ { string? _rowsDataSize = $"height: {ItemSize}px"; - - @for (var colIndex = 0; colIndex < _columns.Count; colIndex++) + + @for (var i = 0; i < _columns.Count; i++) { - var col = _columns[colIndex]; + var col = _columns[i]; - + @((RenderFragment)(__builder => col.RenderPlaceholderContent(__builder, placeholderContext))) } @@ -128,25 +131,23 @@ private void RenderColumnHeaders(RenderTreeBuilder __builder) { - @for (var colIndex = 0; colIndex < _columns.Count; colIndex++) + @for (var i = 0; i < _columns.Count; i++) { - var col = _columns[colIndex]; - var oneBasedIndex = colIndex + 1; - string CellId = Identifier.NewId(); + var col = _columns[i]; + if (_sortByColumn == col) - col.ShowSortIcon = true; + col.IsActiveSortColumn = true; else - col.ShowSortIcon = false; + col.IsActiveSortColumn = false; - + aria-sort="@AriaSortValue(col)"> @col.HeaderContent - @if (HeaderCellAsButtonWithMenu) { @if (col == _displayOptionsForColumn) @@ -161,7 +162,7 @@ @if (ResizeType is not null) { - + }
@@ -173,25 +174,24 @@ {
- @col.ColumnOptions @if (ResizeType is not null) { + @if (@col.ColumnOptions is not null) { } - - } - + @col.ColumnOptions
} } + @if (ResizableColumns) { - + } } @@ -203,9 +203,9 @@ { return; } - // If we use the Blazor components here the renderer gets upset/lost, so we use the web components directly - - + + + @if (EmptyContent is null) { @("No data to show!") @@ -214,18 +214,18 @@ { @EmptyContent } - - + + } private void RenderLoadingContent(RenderTreeBuilder __builder) { - - + + @if (LoadingContent is null) { - +
Loading...
} diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index d795f73b1..8d6942183 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -2,16 +2,16 @@ // MIT License - Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------------------ +using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web.Virtualization; using Microsoft.Extensions.DependencyInjection; using Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; using Microsoft.FluentUI.AspNetCore.Components.Extensions; using Microsoft.FluentUI.AspNetCore.Components.Infrastructure; +using Microsoft.FluentUI.AspNetCore.Components.Utilities; using Microsoft.JSInterop; -using System.Diagnostics.CodeAnalysis; - namespace Microsoft.FluentUI.AspNetCore.Components; /// @@ -80,6 +80,15 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve [Parameter] public bool Virtualize { get; set; } + /// + /// This is applicable only when using . It defines how many additional items will be rendered + /// before and after the visible region to reduce rendering frequency during scrolling. While higher values can improve + /// scroll smoothness by rendering more items off-screen, they can also increase initial load times. Finding a balance + /// based on your data set size and user experience requirements is recommended. The default value is 3. + /// + [Parameter] + public int OverscanCount { get; set; } = 3; + /// /// This is applicable only when using . It defines an expected height in pixels for /// each row, allowing the virtualization mechanism to fetch the correct number of items to match the display @@ -248,6 +257,18 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve [Parameter] public bool AutoFit { get; set; } + /// + /// Gets or sets the size of each row in the grid based on the enum. + /// + [Parameter] + public DataGridRowSize RowSize { get; set; } = DataGridRowSize.Small; + + /// + /// Gets or sets a value indicating whether the grid should allow multiple lines of text in cells. + /// + [Parameter] + public bool MultiLine { get; set; } = false; + /// /// Gets or sets a value indicating whether the grid should save its paging state in the URL. /// @@ -261,6 +282,12 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve [Parameter] public string? SaveStatePrefix { get; set; } + /// + /// Gets or sets a value indicating whether the grids' first cell should be focused. + /// + [Parameter] + public bool AutoFocus{ get; set; } = false; + private ElementReference? _gridReference; private Virtualize<(int, TGridItem)>? _virtualizeComponent; @@ -347,11 +374,7 @@ protected override void OnInitialized() /// protected override Task OnParametersSetAsync() { - if (AutoFit) - { - _internalGridTemplateColumns = "auto-fit"; - } - else + if (GridTemplateColumns is not null) { _internalGridTemplateColumns = GridTemplateColumns; } @@ -394,17 +417,12 @@ protected override async Task OnAfterRenderAsync(bool firstRender) Module ??= await JSRuntime.InvokeAsync("import", JAVASCRIPT_FILE.FormatCollocatedUrl(LibraryConfiguration)); try { - _jsEventDisposable = await Module.InvokeAsync("init", _gridReference); + _jsEventDisposable = await Module.InvokeAsync("init", _gridReference, AutoFocus); } catch (JSException ex) { Console.WriteLine("[FluentDataGrid] " + ex.Message); } - - if (AutoFit && _gridReference is not null) - { - _ = Module?.InvokeVoidAsync("autoFitGridColumns", _gridReference, _columns.Count).AsTask(); - } } if (_checkColumnOptionsPosition && _displayOptionsForColumn is not null) @@ -418,6 +436,11 @@ protected override async Task OnAfterRenderAsync(bool firstRender) _checkColumnResizePosition = false; _ = Module?.InvokeVoidAsync("checkColumnPopupPosition", _gridReference, ".col-resize").AsTask(); } + + if (AutoFit && _gridReference is not null) + { + _ = Module?.InvokeVoidAsync("autoFitGridColumns", _gridReference, _columns.Count).AsTask(); + } } // Invoked by descendant columns at a special time during rendering @@ -425,6 +448,7 @@ internal void AddColumn(ColumnBase column, SortDirection? initialSort { if (_collectingColumns) { + column.Index = _columns.Count + 1; _columns.Add(column); if (isDefaultSortColumn && _sortByColumn is null && initialSortDirection.HasValue) @@ -452,9 +476,18 @@ private void FinishCollectingColumns() throw new Exception("You can use either the 'GridTemplateColumns' parameter on the grid or the 'Width' property at the column level, not both."); } - if (string.IsNullOrWhiteSpace(_internalGridTemplateColumns) && _columns.Any(x => !string.IsNullOrWhiteSpace(x.Width))) + if (string.IsNullOrWhiteSpace(_internalGridTemplateColumns)) { - _internalGridTemplateColumns = string.Join(" ", _columns.Select(x => x.Width ?? "1fr")); + if (!AutoFit) + { + _internalGridTemplateColumns = string.Join(" ", Enumerable.Repeat("1fr", _columns.Count)); + } + + if (_columns.Any(x => !string.IsNullOrWhiteSpace(x.Width))) + { + _internalGridTemplateColumns = string.Join(" ", _columns.Select(x => x.Width ?? "auto")); + } + } if (ResizableColumns) @@ -520,10 +553,11 @@ public Task RemoveSortByColumnAsync(ColumnBase column) { _sortByColumn = _internalGridContext.DefaultSortColumn.Column ?? null; _sortByAscending = _internalGridContext.DefaultSortColumn.Direction != SortDirection.Descending; - } - StateHasChanged(); // We want to see the updated sort order in the header, even before the data query is completed - return RefreshDataCoreAsync(); + StateHasChanged(); // We want to see the updated sort order in the header, even before the data query is completed + return RefreshDataCoreAsync(); + } + return Task.CompletedTask; } /// @@ -751,30 +785,38 @@ private string AriaSortValue(ColumnBase column) ? (_sortByAscending ? "ascending" : "descending") : "none"; + private string? StyleValue => new StyleBuilder(Style) + .AddStyle("grid-template-columns", _internalGridTemplateColumns, !string.IsNullOrWhiteSpace(_internalGridTemplateColumns)) + .AddStyle("grid-template-rows", "auto 1fr", _internalGridContext.Items.Count == 0 || Items is null) + .AddStyle("height", $"calc(100% - {(int)RowSize}px)", _internalGridContext.TotalItemCount == 0 || Loading) + .Build(); + private string? ColumnHeaderClass(ColumnBase column) - => _sortByColumn == column - ? $"{ColumnClass(column)} {(_sortByAscending ? "col-sort-asc" : "col-sort-desc")}" - : ColumnClass(column); + { + return new CssBuilder(Class) + .AddClass(ColumnJustifyClass(column)) + .AddClass("col-sort-asc", _sortByAscending) + .AddClass("col-sort-desc", !_sortByAscending) + .Build(); + } private string? GridClass() { - var value = $"{Class} {(_pendingDataLoadCancellationTokenSource is null ? null : "loading")}".Trim(); - - if (AutoFit) - { - value += " auto-fit"; - } - - return string.IsNullOrEmpty(value) ? null : value; + return new CssBuilder("fluent-data-grid") + .AddClass(Class) + .AddClass("auto-fit", AutoFit) + .AddClass("loading", _pendingDataLoadCancellationTokenSource is not null) + .Build(); } - private static string? ColumnClass(ColumnBase column) => column.Align switch + private static string? ColumnJustifyClass(ColumnBase column) { - Align.Start => $"col-justify-start {column.Class}", - Align.Center => $"col-justify-center {column.Class}", - Align.End => $"col-justify-end {column.Class}", - _ => column.Class, - }; + return new CssBuilder(column.Class) + .AddClass("col-justify-start", column.Align == Align.Start) + .AddClass("col-justify-center", column.Align == Align.Center) + .AddClass("col-justify-end", column.Align == Align.End) + .Build(); + } /// public async ValueTask DisposeAsync() @@ -787,12 +829,12 @@ public async ValueTask DisposeAsync() if (_jsEventDisposable is not null) { await _jsEventDisposable.InvokeVoidAsync("stop"); - await _jsEventDisposable.DisposeAsync(); + await _jsEventDisposable.DisposeAsync().ConfigureAwait(false); } if (Module is not null) { - await Module.DisposeAsync(); + await Module.DisposeAsync().ConfigureAwait(false); } } catch (Exception ex) when (ex is JSDisconnectedException || @@ -838,12 +880,12 @@ private void LoadStateFromQueryString(string queryString) if (Pagination is not null) { - if (query.AllKeys.Contains($"{SaveStatePrefix}page") && int.TryParse(query[$"{SaveStatePrefix}page"]!, out int page)) + if (query.AllKeys.Contains($"{SaveStatePrefix}page") && int.TryParse(query[$"{SaveStatePrefix}page"]!, out var page)) { Pagination.SetCurrentPageIndexAsync(page - 1); } - if (query.AllKeys.Contains($"{SaveStatePrefix}top") && int.TryParse(query[$"{SaveStatePrefix}top"]!, out int itemsPerPage)) + if (query.AllKeys.Contains($"{SaveStatePrefix}top") && int.TryParse(query[$"{SaveStatePrefix}top"]!, out var itemsPerPage)) { Pagination.ItemsPerPage = itemsPerPage; } diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.css b/src/Core/Components/DataGrid/FluentDataGrid.razor.css index d371ef5f6..1122bec7e 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.css +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.css @@ -1,21 +1,27 @@ -fluent-data-grid { - --col-gap: 1rem; +.fluent-data-grid { + width: auto; + flex: 1; + display: grid; + border-collapse: collapse; + align-items: center; + height: max-content; } -::deep .empty-content-row, -::deep .loading-content-row { - width: 100%; - height: 100% !important; - display: flex; - justify-content: center; - align-items: center; +thead, +tbody +{ + display:contents; +} + +::deep tr { + display: contents; } -::deep .empty-content-cell, -::deep .loading-content-cell { - font-weight: 600; +.fluent-data-grid tbody tr .hover { + background: var(--neutral-fill-stealth-hover); } + .col-options, .col-resize { position: absolute; min-width: 250px; @@ -25,6 +31,7 @@ fluent-data-grid { border-radius: 0.3rem; box-shadow: 0 3px 8px 1px var(--neutral-layer-4); padding: 1rem; + visibility: hidden; z-index: 1; } @@ -32,20 +39,19 @@ fluent-data-grid { left: unset; } -.col-justify-end .col-options, .col-justify-right .col-options { +.col-justify-end .col-options, +.col-justify-right .col-options { left: unset; margin-right: 0.6rem; } -[dir=rtl] .col-justify-end .col-options, [dir=rtl] .col-justify-right .col-options { +[dir=rtl] .col-justify-end .col-options, +[dir=rtl] .col-justify-right .col-options { right: unset; margin-left: 0.6rem; } -::deep > fluent-data-grid-row > fluent-data-grid-cell.grid-cell-placeholder:after { - content: '\2026'; /*horizontal ellipsis*/ - opacity: 0.75; -} + .resize-options { display: flex; @@ -54,17 +60,34 @@ fluent-data-grid { align-items: center; } -::deep .col-width-draghandle { - height: auto; - width: calc(var(--design-unit) * 1px + 2px); - right: calc(var(--col-gap)/2 - 0.5rem); - border-top: none; - border-left: calc(var(--stroke-width) * 1px) solid var(--neutral-stroke-divider-rest); +::deep .resize-handle { + position: absolute; + top: 5px; + right: 0; + left: unset; + bottom: 0; + height: 32px; cursor: col-resize; - position: sticky; - min-width: calc(var(--design-unit) * 1px + 2px); + margin-left: calc(var(--design-unit) * 2px); + width: calc(var(--design-unit) * 1px + 2px); +} + +[dir=rtl] * ::deep .resize-handle { + left: 0; + right: unset; } -.col-width-draghandle:hover, .col-width-draghandle:active { - background: var(--neutral-stroke-divider-rest); +.header { + padding: 0; + z-index: 3; } + +::deep tr[row-type='sticky-header'] > th { + position: sticky; + top: 0; + background-color: var(--neutral-fill-stealth-rest); + z-index: 2; +} + + + diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.js b/src/Core/Components/DataGrid/FluentDataGrid.razor.js index 2d2f9b4e6..f53e9a6fe 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.js +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.js @@ -1,13 +1,17 @@ -let initialColumnsWidths = {}; +let grids = []; +const minWidth = 100; -export function init(gridElement) { +export function init(gridElement, autoFocus) { if (gridElement === undefined || gridElement === null) { return; }; - if (gridElement.querySelectorAll('.column-header.resizable').length > 0) { - initialColumnsWidths[gridElement.id] = gridElement.gridTemplateColumns ; - enableColumnResizing(gridElement); + enableColumnResizing(gridElement); + + let start = gridElement.querySelector('td:first-child'); + + if (autoFocus) { + start.focus(); } const bodyClickHandler = event => { @@ -20,6 +24,13 @@ export function init(gridElement) { gridElement.dispatchEvent(new CustomEvent('closecolumnresize', { bubbles: true })); } }; + const keyboardNavigation = (sibling) => { + if (sibling !== null) { + start.focus(); + sibling.focus(); + start = sibling; + } + } const keyDownHandler = event => { const columnOptionsElement = gridElement?.querySelector('.col-options'); if (columnOptionsElement) { @@ -51,6 +62,44 @@ export function init(gridElement) { } ); } + + if (start === document.activeElement) { + const idx = start.cellIndex; + + if (event.key === "ArrowUp") { + // up arrow + const previousRow = start.parentElement.previousElementSibling; + if (previousRow !== null) { + event.preventDefault(); + const previousSibling = previousRow.cells[idx]; + keyboardNavigation(previousSibling); + } + } else if (event.key === "ArrowDown") { + // down arrow + const nextRow = start.parentElement.nextElementSibling; + if (nextRow !== null) { + event.preventDefault(); + const nextSibling = nextRow.cells[idx]; + keyboardNavigation(nextSibling); + } + } else if (event.key === "ArrowLeft") { + // left arrow + event.preventDefault(); + const previousSibling = (document.body.dir === '' || document.body.dir === 'ltr') ? start.previousElementSibling : start.nextElementSibling; + keyboardNavigation(previousSibling); + event.stopPropagation(); + } else if (event.key === "ArrowRight") { + // right arrow + event.preventDefault(); + const nextsibling = (document.body.dir === '' || document.body.dir === 'ltr') ? start.nextElementSibling : start.previousElementSibling; + keyboardNavigation(nextsibling); + event.stopPropagation(); + } + } + else { + start = document.activeElement; + } + }; const cells = gridElement.querySelectorAll('[role="gridcell"]'); @@ -81,74 +130,24 @@ export function init(gridElement) { document.body.removeEventListener('click', bodyClickHandler); document.body.removeEventListener('mousedown', bodyClickHandler); document.body.removeEventListener('keydown', keyDownHandler); - delete initialColumnsWidths[gridElement.id]; + delete grids[gridElement]; } }; } -export function checkColumnOptionsPosition(gridElement) { - const colOptions = gridElement?._rowElements[0] && gridElement?.querySelector('.col-options'); // Only match within *our* thead, not nested tables - if (colOptions) { - // We want the options popup to be positioned over the grid, not overflowing on either side, because it's possible that - // beyond either side is off-screen or outside the scroll range of an ancestor - const gridRect = gridElement.getBoundingClientRect(); - const optionsRect = colOptions.getBoundingClientRect(); - const leftOverhang = Math.max(0, gridRect.left - optionsRect.left); - const rightOverhang = Math.max(0, optionsRect.right - gridRect.right); - if (leftOverhang || rightOverhang) { - // In the unlikely event that it overhangs both sides, we'll center it - const applyOffset = leftOverhang && rightOverhang ? (leftOverhang - rightOverhang) / 2 : (leftOverhang - rightOverhang); - colOptions.style.transform = `translateX(${applyOffset}px)`; - } - - colOptions.scrollIntoViewIfNeeded(); - - const autoFocusElem = colOptions.querySelector('[autofocus]'); - if (autoFocusElem) { - autoFocusElem.focus(); - } - } -} - -export function checkColumnResizePosition(gridElement) { - const colOptions = gridElement?._rowElements[0] && gridElement?.querySelector('.col-resize'); // Only match within *our* thead, not nested tables - if (colResize) { - // We want the options popup to be positioned over the grid, not overflowing on either side, because it's possible that - // beyond either side is off-screen or outside the scroll range of an ancestor - const gridRect = gridElement.getBoundingClientRect(); - const resizeRect = colResize.getBoundingClientRect(); - const leftOverhang = Math.max(0, gridRect.left - resizeRect.left); - const rightOverhang = Math.max(0, resizeRect.right - gridRect.right); - if (leftOverhang || rightOverhang) { - // In the unlikely event that it overhangs both sides, we'll center it - const applyOffset = leftOverhang && rightOverhang ? (leftOverhang - rightOverhang) / 2 : (leftOverhang - rightOverhang); - colResize.style.transform = `translateX(${applyOffset}px)`; - } - - colResize.scrollIntoViewIfNeeded(); - - const autoFocusElem = colResize.querySelector('[autofocus]'); - if (autoFocusElem) { - autoFocusElem.focus(); - } - } -} - export function checkColumnPopupPosition(gridElement, selector) { - const colPopup = gridElement?._rowElements[0] && gridElement?.querySelector(selector); // Only match within *our* thead, not nested tables + const colPopup = gridElement.querySelector(selector); if (colPopup) { - // We want the options popup to be positioned over the grid, not overflowing on either side, because it's possible that - // beyond either side is off-screen or outside the scroll range of an ancestor const gridRect = gridElement.getBoundingClientRect(); const popupRect = colPopup.getBoundingClientRect(); const leftOverhang = Math.max(0, gridRect.left - popupRect.left); const rightOverhang = Math.max(0, popupRect.right - gridRect.right); if (leftOverhang || rightOverhang) { - // In the unlikely event that it overhangs both sides, we'll center it const applyOffset = leftOverhang && rightOverhang ? (leftOverhang - rightOverhang) / 2 : (leftOverhang - rightOverhang); colPopup.style.transform = `translateX(${applyOffset}px)`; } + colPopup.style.visibility = 'visible'; colPopup.scrollIntoViewIfNeeded(); const autoFocusElem = colPopup.querySelector('[autofocus]'); @@ -157,59 +156,63 @@ export function checkColumnPopupPosition(gridElement, selector) { } } } + export function enableColumnResizing(gridElement) { const columns = []; let min = 75; let headerBeingResized; let resizeHandle; - gridElement.querySelectorAll('.column-header.resizable').forEach(header => { - columns.push({ header }); + const headers = gridElement.querySelectorAll('.column-header.resizable'); + + if (headers.length === 0) { + return; + } + + headers.forEach(header => { + columns.push({ + header, + size: `minmax(${minWidth}px,1fr)`, + }); + const onPointerMove = (e) => requestAnimationFrame(() => { if (!headerBeingResized) { return; } - let gridEdge; - let headerLocalEdge; - let pointerLocalEdge; + const horizontalScrollOffset = document.documentElement.scrollLeft; + let width; if (document.body.dir === '' || document.body.dir === 'ltr') { - gridEdge = gridElement.getBoundingClientRect().left; - headerLocalEdge = headerBeingResized.getBoundingClientRect().left - gridEdge; - pointerLocalEdge = e.clientX - gridEdge; + width = (horizontalScrollOffset + e.clientX) - headerBeingResized.offsetLeft; } else { - gridEdge = gridElement.getBoundingClientRect().right; - headerLocalEdge = gridEdge - headerBeingResized.getBoundingClientRect().right; - pointerLocalEdge = gridEdge - e.clientX; + width = headerBeingResized.offsetLeft + headerBeingResized.clientWidth - (horizontalScrollOffset + e.clientX); } - const width = pointerLocalEdge - headerLocalEdge; const column = columns.find(({ header }) => header === headerBeingResized); - min = header.querySelector('.col-options-button') ? 100 : 75; + column.size = Math.max(minWidth, width) + 'px'; - column.size = Math.max(min, width) + 'px'; - - // Set initial sizes columns.forEach((column) => { - if (column.size === undefined) { - if (column.header.clientWidth === undefined || column.header.clientWidth === 0) { - column.size = '50px'; - } else { - column.size = column.header.clientWidth + 'px'; - } + if (column.size.startsWith('minmax')) { + column.size = parseInt(column.header.clientWidth, 10) + 'px'; } }); - gridElement.gridTemplateColumns = columns + gridElement.style.gridTemplateColumns = columns .map(({ size }) => size) .join(' '); }); const onPointerUp = (e) => { - headerBeingResized = undefined; - resizeHandle = undefined; + + window.removeEventListener('pointermove', onPointerMove); + window.removeEventListener('pointerup', onPointerUp); + window.removeEventListener('pointercancel', onPointerUp); + window.removeEventListener('pointerleave', onPointerUp); + + headerBeingResized.classList.remove('header--being-resized'); + headerBeingResized = null; if (e.target.hasPointerCapture(e.pointerId)) { e.target.releasePointerCapture(e.pointerId); @@ -217,143 +220,142 @@ export function enableColumnResizing(gridElement) { }; const initResize = ({ target, pointerId }) => { - resizeHandle = target; headerBeingResized = target.parentNode; + headerBeingResized.classList.add('header--being-resized'); + + + window.addEventListener('pointermove', onPointerMove); + window.addEventListener('pointerup', onPointerUp); + window.addEventListener('pointercancel', onPointerUp); + window.addEventListener('pointerleave', onPointerUp); if (resizeHandle) { resizeHandle.setPointerCapture(pointerId); } }; - const dragHandle = header.querySelector('.col-width-draghandle'); - if (dragHandle) { - dragHandle.addEventListener('pointerdown', initResize); - dragHandle.addEventListener('pointermove', onPointerMove); - dragHandle.addEventListener('pointerup', onPointerUp); - dragHandle.addEventListener('pointercancel', onPointerUp); - dragHandle.addEventListener('pointerleave', onPointerUp); - } + header.querySelector('.resize-handle').addEventListener('pointerdown', initResize); + + }); + + let initialWidths; + if (gridElement.style.gridTemplateColumns) { + initialWidths = gridElement.style.gridTemplateColumns; + } + else { + initialWidths = columns + .map(({ header, size }) => size) + .join(' '); + + gridElement.style.gridTemplateColumns = initialWidths; + } + + let id = gridElement.id; + grids.push({ + id, + columns, + initialWidths }); } export function resetColumnWidths(gridElement) { - gridElement.gridTemplateColumns = initialColumnsWidths[gridElement.id]; + const grid = grids.find(({ id }) => id === gridElement.id); + if (!grid) { + return; + } + + const columnsWidths = grid.initialWidths.split(' '); + + grid.columns.forEach((column, index) => { + column.size = columnsWidths[index]; + }); + + gridElement.style.gridTemplateColumns = grid.initialWidths; + gridElement.dispatchEvent(new CustomEvent('closecolumnresize', { bubbles: true })); + gridElement.focus(); } export function resizeColumnDiscrete(gridElement, column, change) { - let headers = gridElement.querySelectorAll('.column-header.resizable'); - if (headers.length <= 0) { - return - } - + const columns = []; let headerBeingResized; - if (!column) { - if (!(document.activeElement.classList.contains("column-header") && document.activeElement.classList.contains("resizable"))) { + if (!column) { + const targetElement = document.activeElement.parentElement.parentElement.parentElement.parentElement; + if (!(targetElement.classList.contains("column-header") && targetElement.classList.contains("resizable"))) { return; } - headerBeingResized = document.activeElement; + headerBeingResized = targetElement; } else { - headerBeingResized = gridElement.querySelector('.column-header[grid-column="' + column + '"]'); + headerBeingResized = gridElement.querySelector('.column-header[col-index="' + column + '"]'); } - const columns = []; - let min = 50; - - headers.forEach(header => { - if (header === headerBeingResized) { - min = headerBeingResized.querySelector('.col-options-button') ? 75 : 50; + grids.find(({ id }) => id === gridElement.id).columns.forEach(column => { + if (column.header === headerBeingResized) { const width = headerBeingResized.getBoundingClientRect().width + change; if (change < 0) { - header.size = Math.max(min, width) + 'px'; + column.size = Math.max(minWidth, width) + 'px'; } else { - header.size = width + 'px'; + column.size = width + 'px'; } } else { - if (header.size === undefined) { - if (header.clientWidth === undefined || header.clientWidth === 0) { - header.size = min + 'px'; - } else { - header.size = header.clientWidth + 'px'; - } + if (column.size.startsWith('minmax')) { + column.size = parseInt(column.header.clientWidth, 10) + 'px'; } } - - columns.push({ header }); + columns.push(column.size); }); - gridElement.gridTemplateColumns = columns - .map(({ header }) => header.size) - .join(' '); -} - -export function autoFitGridColumns(gridElement, columnCount) { - let gridTemplateColumns = ''; - - for (var i = 0; i < columnCount; i++) { - const columnWidths = Array - .from(gridElement.querySelectorAll(`[grid-column="${i + 1}"]`)) - .flatMap((x) => x.offsetWidth); - - const maxColumnWidth = Math.max(...columnWidths); - - gridTemplateColumns += ` ${maxColumnWidth}fr`; - } - - gridElement.setAttribute("grid-template-columns", gridTemplateColumns); - gridElement.classList.remove("auto-fit"); - - initialColumnsWidths[gridElement.id] = gridTemplateColumns; + gridElement.style.gridTemplateColumns = columns.join(' '); } export function resizeColumnExact(gridElement, column, width) { + const columns = []; + let headerBeingResized = gridElement.querySelector('.column-header[col-index="' + column + '"]'); - let headers = gridElement.querySelectorAll('.column-header.resizable'); - if (headers.length <= 0) { - return - } - - let headerBeingResized = gridElement.querySelector('.column-header[grid-column="' + column + '"]'); if (!headerBeingResized) { return; } - const columns = []; - let min = 50; - - headers.forEach(header => { - if (header === headerBeingResized) { - min = headerBeingResized.querySelector('.col-options-button') ? 75 : 50; - - const newWidth = width; - - header.size = Math.max(min, newWidth) + 'px'; + grids.find(({ id }) => id === gridElement.id).columns.forEach(column => { + if (column.header === headerBeingResized) { + column.size = Math.max(minWidth, width) + 'px'; } else { - if (header.size === undefined) { - if (header.clientWidth === undefined || header.clientWidth === 0) { - header.size = min + 'px'; - } else { - header.size = header.clientWidth + 'px'; - } + if (column.size.startsWith('minmax')) { + column.size = parseInt(column.header.clientWidth, 10) + 'px'; } } - - columns.push({ header }); + columns.push(column.size); }); - gridElement.gridTemplateColumns = columns - .map(({ header }) => header.size) - .join(' '); + gridElement.style.gridTemplateColumns = columns.join(' '); - gridElement.dispatchEvent(new CustomEvent('closecolumnoptions', { bubbles: true })); gridElement.dispatchEvent(new CustomEvent('closecolumnresize', { bubbles: true })); gridElement.focus(); } + +export function autoFitGridColumns(gridElement, columnCount) { + let gridTemplateColumns = ''; + + for (var i = 0; i < columnCount; i++) { + const columnWidths = Array + .from(gridElement.querySelectorAll(`[col-index="${i + 1}"]`)) + .flatMap((x) => x.offsetWidth); + + const maxColumnWidth = Math.max(...columnWidths); + + gridTemplateColumns += ` ${maxColumnWidth}px`; + } + + gridElement.style.gridTemplateColumns = gridTemplateColumns; + gridElement.classList.remove("auto-fit"); + + grids[gridElement.id] = gridTemplateColumns; +} diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor b/src/Core/Components/DataGrid/FluentDataGridCell.razor index a05de6cc3..c531716d0 100644 --- a/src/Core/Components/DataGrid/FluentDataGridCell.razor +++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor @@ -1,13 +1,29 @@ @namespace Microsoft.FluentUI.AspNetCore.Components @inherits FluentComponentBase @typeparam TGridItem - - @ChildContent - + +@if (CellType == DataGridCellType.Default) +{ + + @ChildContent + +} +else +{ + + @ChildContent + +} diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs index c81423a88..327a9339c 100644 --- a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs @@ -23,7 +23,7 @@ public partial class FluentDataGridCell : FluentComponentBase /// Gets or sets the cell type. See . /// [Parameter] - public DataGridCellType? CellType { get; set; } = DataGridCellType.Default; + public DataGridCellType CellType { get; set; } /// /// Gets or sets the column index of the cell. @@ -48,17 +48,38 @@ public partial class FluentDataGridCell : FluentComponentBase /// Gets or sets the owning component /// [CascadingParameter] - private InternalGridContext GridContext { get; set; } = default!; + internal InternalGridContext InternalGridContext { get; set; } = default!; /// /// Gets a reference to the column that this cell belongs to. /// - private ColumnBase? Column => Owner.Owner.Grid._columns.ElementAtOrDefault(GridColumn - 1); + private ColumnBase? Column => Grid._columns.ElementAtOrDefault(GridColumn - 1); + + /// + /// Gets a reference to the enclosing . + /// + protected FluentDataGrid Grid => InternalGridContext.Grid; + + protected string? ClassValue => new CssBuilder(Class) + .AddClass("column-header", when: CellType == DataGridCellType.ColumnHeader) + .AddClass("select-all", when: CellType == DataGridCellType.ColumnHeader && Column is SelectColumn) + .AddClass("multiline-text", when: Grid.MultiLine) + .AddClass(Owner.Class) + .Build(); protected string? StyleValue => new StyleBuilder(Style) - .AddStyle("height", $"{GridContext.Grid.ItemSize:0}px", () => !GridContext.Grid.Loading && GridContext.Grid.Virtualize && Owner.RowType == DataGridRowType.Default) - .AddStyle("align-content", "center", () => !GridContext.Grid.Loading && GridContext.Grid.Virtualize && Owner.RowType == DataGridRowType.Default && string.IsNullOrEmpty(Style)) - .Build(); + .AddStyle("grid-column", GridColumn.ToString(), () => (!Grid.Loading && Grid.Items is not null) || Grid.Virtualize) + .AddStyle("text-align", "center", Column is SelectColumn) + .AddStyle("align-content", "center", Column is SelectColumn) + .AddStyle("padding-inline-start", "calc(((var(--design-unit)* 3) + var(--focus-stroke-width) - var(--stroke-width))* 1px)", Column is SelectColumn && Owner.RowType == DataGridRowType.Default) + .AddStyle("padding-top", "calc(var(--design-unit) * 2.5px)", Column is SelectColumn && (Grid.RowSize == DataGridRowSize.Medium || Owner.RowType == DataGridRowType.Header)) + .AddStyle("padding-top", "calc(var(--design-unit) * 1.5px)", Column is SelectColumn && Grid.RowSize == DataGridRowSize.Small && Owner.RowType == DataGridRowType.Default) + .AddStyle("height", $"{Grid.ItemSize:0}px", () => !Grid.Loading && Grid.Virtualize && Owner.RowType == DataGridRowType.Default) + .AddStyle("height", $"{(int)Grid.RowSize}px", () => !Grid.Loading && !Grid.Virtualize && Grid.Items is not null && !Grid.MultiLine) + .AddStyle("height", "100%", InternalGridContext.TotalItemCount == 0 || (Grid.Loading && Grid.Items is null) || Grid.MultiLine) + .AddStyle("min-height", "44px", Owner.RowType != DataGridRowType.Default) + .AddStyle(Owner.Style) + .Build(); protected override void OnInitialized() { @@ -68,9 +89,9 @@ protected override void OnInitialized() /// internal async Task HandleOnCellClickAsync() { - if (GridContext.Grid.OnCellClick.HasDelegate) + if (Grid.OnCellClick.HasDelegate) { - await GridContext.Grid.OnCellClick.InvokeAsync(this); + await Grid.OnCellClick.InvokeAsync(this); } if (Column != null) diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor.css b/src/Core/Components/DataGrid/FluentDataGridCell.razor.css index e866d970d..b7285afe0 100644 --- a/src/Core/Components/DataGrid/FluentDataGridCell.razor.css +++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor.css @@ -1,123 +1,105 @@ -fluent-data-grid-cell { - text-overflow: ellipsis; -} - -.auto-fit fluent-data-grid-cell { - text-overflow: unset; - overflow: visible; - visibility: hidden; +th, td { + border-bottom: calc(var(--stroke-width)* 1px) solid var(--neutral-stroke-divider-rest); } -.multiline-text { - white-space: inherit; - overflow: auto; - word-break: break-word; +td { + padding: calc((var(--design-unit) + var(--focus-stroke-width) - var(--stroke-width))* 1px) calc(((var(--design-unit)* 3) + var(--focus-stroke-width) - var(--stroke-width))* 1px); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + align-content: center; } -.column-header { - display: flex; - width: 100%; - align-self: center; - padding-inline: 0; - padding-right: 1px; - padding-left: 1px; -} - .column-header.col-justify-end, .column-header.col-justify-right { - padding-right: 1px; - padding-left: 1px; - justify-content: end; + td.col-justify-center { + text-align: center; + padding-inline-start: 0; } - .column-header.col-justify-center { - padding-left: 1px; - padding-right: 1px; - justify-content: center; + td.col-justify-end, + td.col-justify-right { + text-align: end; + padding-inline-end: calc(var(--design-unit) * 7px); } - .column-header > ::deep .keycapture { - display: flex; - flex-grow: 1; + td.grid-cell-placeholder:after { + content: '\2026'; /*horizontal ellipsis*/ + opacity: 0.75; } - .column-header > ::deep .keycapture .col-sort-container { - display: flex; - flex-grow: 1; - } - .column-header > ::deep .keycapture .col-sort-container .col-sort-button { - flex-grow: 1; - padding-inline-end: 5px; - } - - .column-header > ::deep .keycapture .col-sort-container .col-sort-button::part(content) { - overflow: hidden; - text-overflow: ellipsis; - } +.empty-content-cell, +.loading-content-cell { + font-weight: 600; + text-align: center; + height: 100%; +} - .column-header.col-justify-end ::deep .col-title-text, .column-header.col-justify-right ::deep .col-title-text { - text-align: end; - padding-left: 4px; - } +.multiline-text { + white-space: inherit; + overflow: auto; + word-break: break-word; + align-content: start; +} - .column-header.col-justify-end ::deep .col-sort-button, .column-header.col-justify-right ::deep .col-sort-button { - justify-content: end; - } +::deep .col-header-content { + display: flex; + flex-grow: 1; +} - .column-header.col-justify-center ::deep .col-title-text { - text-align: center; - } +.column-header { + font-weight: 600; + position: relative; + padding: calc((var(--design-unit) + var(--focus-stroke-width) - var(--stroke-width)) * 1px) 1px calc((var(--design-unit) + var(--focus-stroke-width) - var(--stroke-width)) * 1px); +} - .column-header.col-justify-end ::deep .col-sort-button, .column-header.col-justify-right ::deep .col-sort-button { - justify-content: end; - text-align: end; - } +.column-header:not(.select-all):first-of-type { + padding-inline-start: calc(var(--design-unit) * 2px); +} -::deep .col-sort-button::part(control) { - align-items: center; - justify-content: start; +::deep .col-sort-button { + width: calc(100% - 20px); overflow: hidden; text-overflow: ellipsis; } -::deep .col-sort-button.disabled::part(control) { - opacity: 1 !important; + ::deep .col-sort-button::part(content) { + overflow: hidden; + } + +.col-justify-start ::deep .col-sort-button::part(control) { + justify-content: start; + overflow: hidden; + opacity: 1 } .col-justify-center ::deep .col-sort-button::part(control) { justify-content: center; + overflow: hidden; + opacity: 1 } -.col-justify-end ::deep .col-sort-button::part(control), .col-justify-right ::deep .col-sort-button::part(control) { +.col-justify-end ::deep .col-sort-button::part(control) { justify-content: end; + overflow: hidden; + opacity: 1 } -.col-justify-end ::deep .col-sort-button::part(start), .col-justify-right ::deep .col-sort-button::part(start) { - margin-inline-end: 0 !important; -} -.col-justify-center { - justify-self: center; +.col-justify-end ::deep .col-sort-button::part(start) { + margin-inline-end: 2px; } -.col-justify-end, .col-justify-right { - justify-self: end; - padding-inline-end: 20px; +.col-justify-start ::deep .col-sort-button::part(end), +.col-justify-center ::deep .col-sort-button::part(end) { + margin-inline-start: 2px; } - .col-justify-end .col-title, .col-justify-right .col-title { - justify-content: end; - } - -::deep .col-title { - padding: 0 calc((12 + (var(--design-unit) * 2 * var(--density))) * 1px); - display: flex; - align-items: center; +.col-justify-center ::deep .col-title { + text-align: center; } -::deep .col-title-text { - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - font-weight: 600; +.col-justify-end ::deep .col-title { + text-align: end; + margin-inline-end: calc(var(--design-unit) * 4px); } diff --git a/src/Core/Components/DataGrid/FluentDataGridRow.razor b/src/Core/Components/DataGrid/FluentDataGridRow.razor index 809c81781..ab842f60d 100644 --- a/src/Core/Components/DataGrid/FluentDataGridRow.razor +++ b/src/Core/Components/DataGrid/FluentDataGridRow.razor @@ -3,17 +3,14 @@ @typeparam TGridItem @attribute [CascadingTypeParameter(nameof(TGridItem))] - + @ChildContent - + diff --git a/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs b/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs index c17580ed5..10fb0db41 100644 --- a/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs @@ -56,30 +56,34 @@ public partial class FluentDataGridRow : FluentComponentBase, IHandle /// Gets or sets the owning component /// [CascadingParameter] - internal InternalGridContext Owner { get; set; } = default!; + internal InternalGridContext InternalGridContext { get; set; } = default!; + + /// + /// Gets a reference to the enclosing . + /// + protected FluentDataGrid Grid => InternalGridContext.Grid; protected string? ClassValue => new CssBuilder(Class) - .AddClass("hover", when: Owner.Grid.ShowHover) + .AddClass("hover", when: Grid.ShowHover) .Build(); protected string? StyleValue => new StyleBuilder(Style) - .AddStyle("height", $"{Owner.Grid.ItemSize:0}px", () => Owner.Grid.Virtualize && RowType == DataGridRowType.Default) - .AddStyle("height", "100%", () => (!Owner.Grid.Virtualize || Owner.Rows.Count == 0) && Owner.Grid.Loading && RowType == DataGridRowType.Default) - .AddStyle("align-items", "center", () => Owner.Grid.Virtualize && RowType == DataGridRowType.Default && string.IsNullOrEmpty(Style)) + //.AddStyle("height", $"{Grid.ItemSize:0}px", () => Grid.Virtualize && RowType == DataGridRowType.Default) + //.AddStyle("height", "100%", () => (!Grid.Virtualize || InternalGridContext.Rows.Count == 0) && Grid.Loading && RowType == DataGridRowType.Default) .Build(); protected override void OnInitialized() { - RowId = $"r{Owner.GetNextRowId()}"; - Owner.Register(this); + RowId = $"r{InternalGridContext.GetNextRowId()}"; + InternalGridContext.Register(this); } - public void Dispose() => Owner.Unregister(this); + public void Dispose() => InternalGridContext.Unregister(this); internal void Register(FluentDataGridCell cell) { - cell.CellId = $"c{Owner.GetNextCellId()}"; + cell.CellId = $"c{InternalGridContext.GetNextCellId()}"; cells.Add(cell.CellId, cell); } @@ -95,7 +99,7 @@ private async Task HandleOnCellFocusAsync(DataGridCellFocusEventArgs args) { if (cell != null && cell.CellType == DataGridCellType.Default) { - await Owner.Grid.OnCellFocus.InvokeAsync(cell); + await Grid.OnCellFocus.InvokeAsync(cell); } } } @@ -105,14 +109,14 @@ internal async Task HandleOnRowClickAsync(string rowId) { var row = GetRow(rowId); - if (row != null && Owner.Grid.OnRowClick.HasDelegate) + if (row != null && Grid.OnRowClick.HasDelegate) { - await Owner.Grid.OnRowClick.InvokeAsync(row); + await Grid.OnRowClick.InvokeAsync(row); } if (row != null && row.RowType == DataGridRowType.Default) { - foreach (var column in Owner.Grid._columns) + foreach (var column in Grid._columns) { await column.OnRowClickAsync(row); } @@ -123,9 +127,9 @@ internal async Task HandleOnRowClickAsync(string rowId) internal async Task HandleOnRowDoubleClickAsync(string rowId) { var row = GetRow(rowId); - if (row != null && Owner.Grid.OnRowDoubleClick.HasDelegate) + if (row != null && Grid.OnRowDoubleClick.HasDelegate) { - await Owner.Grid.OnRowDoubleClick.InvokeAsync(row); + await Grid.OnRowDoubleClick.InvokeAsync(row); } } @@ -139,14 +143,14 @@ internal async Task HandleOnRowKeyDownAsync(string rowId, KeyboardEventArgs e) var row = GetRow(rowId); - if (row != null && Owner.Grid.OnRowClick.HasDelegate) + if (row != null && Grid.OnRowClick.HasDelegate) { - await Owner.Grid.OnRowClick.InvokeAsync(row); + await Grid.OnRowClick.InvokeAsync(row); } if (row != null && row.RowType == DataGridRowType.Default) { - foreach (var column in Owner.Grid._columns) + foreach (var column in Grid._columns) { await column.OnRowKeyDownAsync(row, e); } @@ -155,7 +159,7 @@ internal async Task HandleOnRowKeyDownAsync(string rowId, KeyboardEventArgs e) private FluentDataGridRow? GetRow(string rowId, Func, bool>? where = null) { - if (!string.IsNullOrEmpty(rowId) && Owner.Rows.TryGetValue(rowId, out var row)) + if (!string.IsNullOrEmpty(rowId) && InternalGridContext.Rows.TryGetValue(rowId, out var row)) { return where == null ? row diff --git a/src/Core/Components/DataGrid/FluentDataGridRow.razor.css b/src/Core/Components/DataGrid/FluentDataGridRow.razor.css index e206f8b60..071d1c6a0 100644 --- a/src/Core/Components/DataGrid/FluentDataGridRow.razor.css +++ b/src/Core/Components/DataGrid/FluentDataGridRow.razor.css @@ -1,13 +1,10 @@ -.header { - padding: 0; - z-index: 3; -} - .sticky-header { z-index: 3; } -.hover:not([row-type='header'],[row-type='sticky-header'],.loading-content-row):hover { +.hover:not([row-type='header'],[row-type='sticky-header'],.loading-content-row):hover ::deep td { cursor: pointer; background-color: var(--datagrid-hover-color, var(--neutral-fill-stealth-hover)); } + + diff --git a/src/Core/Components/Icons/CoreIcons.cs b/src/Core/Components/Icons/CoreIcons.cs index 1c577f3f3..96c19ec91 100644 --- a/src/Core/Components/Icons/CoreIcons.cs +++ b/src/Core/Components/Icons/CoreIcons.cs @@ -80,10 +80,14 @@ internal static partial class Regular [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static partial class Size16 { + public class ArrowSortDown : Icon { public ArrowSortDown() : base("ArrowSortDown", IconVariant.Regular, IconSize.Size16, "") { } } + public class ArrowSortUp : Icon { public ArrowSortUp() : base("ArrowSortUp", IconVariant.Regular, IconSize.Size16, "") { } } public class CheckmarkCircle : Icon { public CheckmarkCircle() : base("CheckmarkCircle", IconVariant.Regular, IconSize.Size16, "") { } } public class Dismiss : Icon { public Dismiss() : base("Dismiss", IconVariant.Regular, IconSize.Size16, "") { } } public class DismissCircle : Icon { public DismissCircle() : base("DismissCircle", IconVariant.Regular, IconSize.Size16, "") { } } + public class Filter : Icon { public Filter() : base("Filter", IconVariant.Regular, IconSize.Size16, "") { } } public class Info : Icon { public Info() : base("Info", IconVariant.Regular, IconSize.Size16, "") { } } + public class MoreVertical : Icon { public MoreVertical() : base("MoreVertical", IconVariant.Regular, IconSize.Size16, "") { } } public class Warning : Icon { public Warning() : base("Warning", IconVariant.Regular, IconSize.Size16, "") { } } public class Search : Icon { public Search() : base("Search", IconVariant.Regular, IconSize.Size16, "") { } } } @@ -96,12 +100,19 @@ internal static partial class Size20 { public class Add : Icon { public Add() : base("Add", IconVariant.Regular, IconSize.Size20, "") { } } public class ArrowReset : Icon { public ArrowReset() : base("ArrowReset", IconVariant.Regular, IconSize.Size20, "") { } } + public class ArrowSortDown : Icon { public ArrowSortDown() : base("ArrowSortDown", IconVariant.Regular, IconSize.Size20, "") { } } + public class ArrowSortUp : Icon { public ArrowSortUp() : base("ArrowSortUp", IconVariant.Regular, IconSize.Size20, "") { } } + public class CheckboxUnchecked : Icon { public CheckboxUnchecked() : base("CheckboxUnchecked", IconVariant.Regular, IconSize.Size20, "") { } } public class Checkmark : Icon { public Checkmark() : base("Checkmark", IconVariant.Regular, IconSize.Size24, "") { } } public class ChevronDoubleLeft : Icon { public ChevronDoubleLeft() : base("ChevronDoubleLeft", IconVariant.Regular, IconSize.Size20, "") { } } public class ChevronDoubleRight : Icon { public ChevronDoubleRight() : base("ChevronDoubleRight", IconVariant.Regular, IconSize.Size20, "") { } } + public class ChevronDown : Icon { public ChevronDown() : base("ChevronDown", IconVariant.Regular, IconSize.Size20, "") { } } public class Dismiss : Icon { public Dismiss() : base("Dismiss", IconVariant.Regular, IconSize.Size20, "") { } } public class DismissCircle : Icon { public DismissCircle() : base("DismissCircle", IconVariant.Regular, IconSize.Size20, "") { } } - public class CheckboxUnchecked : Icon { public CheckboxUnchecked() : base("CheckboxUnchecked", IconVariant.Regular, IconSize.Size20, "") { } } + public class Filter : Icon { public Filter() : base("Filter", IconVariant.Regular, IconSize.Size20, "") { } } + public class FilterDismiss : Icon { public FilterDismiss() : base("FilterDismiss", IconVariant.Regular, IconSize.Size20, "") { } } + public class MoreVertical : Icon { public MoreVertical() : base("MoreVertical", IconVariant.Regular, IconSize.Size20, "") { } } + public class RadioButton : Icon { public RadioButton() : base("RadioButton", IconVariant.Regular, IconSize.Size20, "") { } }; public class Star : Icon { public Star() : base("Star", IconVariant.Regular, IconSize.Size20, "") { } }; public class Subtract : Icon { public Subtract() : base("Subtract", IconVariant.Regular, IconSize.Size20, "") { } } diff --git a/src/Core/Components/Menu/FluentMenuProvider.razor b/src/Core/Components/Menu/FluentMenuProvider.razor index beb39d4e3..42ade77fb 100644 --- a/src/Core/Components/Menu/FluentMenuProvider.razor +++ b/src/Core/Components/Menu/FluentMenuProvider.razor @@ -13,8 +13,7 @@ Class="@Class" Style="@Style" Width="@item.Width" - Open="@item.Open" - OpenChanged="@item.OpenChanged" + @bind-Open="@item.Open" Data="@item.Data" ParentReference="@item.ParentReference" Trigger="@item.Trigger" diff --git a/src/Core/Enums/DataGridRowSize.cs b/src/Core/Enums/DataGridRowSize.cs new file mode 100644 index 000000000..4e1cadce6 --- /dev/null +++ b/src/Core/Enums/DataGridRowSize.cs @@ -0,0 +1,34 @@ +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// The height of each in a . +/// Values are in pixels. +/// +public enum DataGridRowSize +{ + /// + /// Small row height (default) + /// + Small = 32, + + /// + /// Medium row height + /// + Medium = 44, + + + /// + /// Smaller row height + /// + Smaller = 24, + + /// + /// Large row height + /// + Large = 58, + + ///// + ///// Dynamic row height + ///// + //Dynamic +} diff --git a/src/Core/Events/EventHandlers.cs b/src/Core/Events/EventHandlers.cs index de4a14a15..a499690ae 100644 --- a/src/Core/Events/EventHandlers.cs +++ b/src/Core/Events/EventHandlers.cs @@ -14,8 +14,6 @@ namespace Microsoft.FluentUI.AspNetCore.Components; [EventHandler("onmenuchange", typeof(MenuChangeEventArgs), enableStopPropagation: true, enablePreventDefault: true)] [EventHandler("onscrollstart", typeof(HorizontalScrollEventArgs), enableStopPropagation: true, enablePreventDefault: true)] [EventHandler("onscrollend", typeof(HorizontalScrollEventArgs), enableStopPropagation: true, enablePreventDefault: true)] -[EventHandler("oncellfocus", typeof(DataGridCellFocusEventArgs), enableStopPropagation: true, enablePreventDefault: true)] -[EventHandler("onrowfocus", typeof(DataGridRowFocusEventArgs), enableStopPropagation: true, enablePreventDefault: true)] [EventHandler("onclosecolumnoptions", typeof(EventArgs), enableStopPropagation: true, enablePreventDefault: true)] [EventHandler("onclosecolumnresize", typeof(EventArgs), enableStopPropagation: true, enablePreventDefault: true)] [EventHandler("ontooltipdismiss", typeof(EventArgs), enableStopPropagation: true, enablePreventDefault: true)] diff --git a/tests/Core/DataGrid/DataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Ascending.verified.razor.html b/tests/Core/DataGrid/DataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Ascending.verified.razor.html index 54e3b7df6..668b64c43 100644 --- a/tests/Core/DataGrid/DataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Ascending.verified.razor.html +++ b/tests/Core/DataGrid/DataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Ascending.verified.razor.html @@ -1,31 +1,35 @@ - - - -
-
Item1
-
-
- -
-
Item2
-
-
-
- - A - D - - - B - C - - - C - B - - - D - A - -
\ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
Item1
+
+
+
+
Item2
+
+
AD
BC
CB
DA
\ No newline at end of file diff --git a/tests/Core/DataGrid/DataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Descending.verified.razor.html b/tests/Core/DataGrid/DataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Descending.verified.razor.html index 7c988e149..59121cb25 100644 --- a/tests/Core/DataGrid/DataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Descending.verified.razor.html +++ b/tests/Core/DataGrid/DataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Descending.verified.razor.html @@ -1,31 +1,35 @@ - - - -
-
Item1
-
-
- -
-
Item2
-
-
-
- - D - A - - - C - B - - - B - C - - - A - D - -
\ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
Item1
+
+
+
+
Item2
+
+
DA
CB
BC
AD
\ No newline at end of file diff --git a/tests/Core/DataGrid/DataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Ascending.verified.razor.html b/tests/Core/DataGrid/DataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Ascending.verified.razor.html index 07643b861..668b64c43 100644 --- a/tests/Core/DataGrid/DataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Ascending.verified.razor.html +++ b/tests/Core/DataGrid/DataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Ascending.verified.razor.html @@ -1,31 +1,35 @@ - - - -
-
Item1
-
-
- -
-
Item2
-
-
-
- - A - D - - - B - C - - - C - B - - - D - A - -
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
Item1
+
+
+
+
Item2
+
+
AD
BC
CB
DA
\ No newline at end of file diff --git a/tests/Core/DataGrid/DataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Descending.verified.razor.html b/tests/Core/DataGrid/DataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Descending.verified.razor.html index 7c988e149..59121cb25 100644 --- a/tests/Core/DataGrid/DataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Descending.verified.razor.html +++ b/tests/Core/DataGrid/DataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Descending.verified.razor.html @@ -1,31 +1,35 @@ - - - -
-
Item1
-
-
- -
-
Item2
-
-
-
- - D - A - - - C - B - - - B - C - - - A - D - -
\ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
Item1
+
+
+
+
Item2
+
+
DA
CB
BC
AD
\ No newline at end of file diff --git a/tests/Core/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering.verified.razor.html b/tests/Core/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering.verified.razor.html index 7179d1af2..6bd3fb6c6 100644 --- a/tests/Core/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering.verified.razor.html +++ b/tests/Core/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering.verified.razor.html @@ -1,26 +1,30 @@ - - - -
-
- -
-
Name
-
-
-
- - - - Jean Martin - - - - Kenji Sato - - - - Julie Smith - -
\ No newline at end of file + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
Name
+
+
+ Jean Martin
Kenji Sato
Julie Smith
diff --git a/tests/Core/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Rendering.verified.razor.html b/tests/Core/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Rendering.verified.razor.html index 87edd83d2..45c4837ee 100644 --- a/tests/Core/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Rendering.verified.razor.html +++ b/tests/Core/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Rendering.verified.razor.html @@ -1,44 +1,48 @@ - - - - - - -
-
Name
-
-
-
- - - - - Jean Martin - - - - - - Kenji Sato - - - - - - Julie Smith - -
\ No newline at end of file + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
Name
+
+
+ + Jean Martin
+ + Kenji Sato
+ + Julie Smith
diff --git a/tests/Core/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleSelect_Rendering.verified.razor.html b/tests/Core/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleSelect_Rendering.verified.razor.html index a2b8b67e5..d59aed370 100644 --- a/tests/Core/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleSelect_Rendering.verified.razor.html +++ b/tests/Core/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleSelect_Rendering.verified.razor.html @@ -1,38 +1,42 @@ - - - - -
-
Name
-
-
-
- - - - - Jean Martin - - - - - - Kenji Sato - - - - - - Julie Smith - -
+ + + + + + + + + + + + + + + + + + + + + +
+
+
Name
+
+
+ + Jean Martin
+ + Kenji Sato
+ + Julie Smith