From c9ce5506fe549702b828ee27af537e5640867f9d Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Fri, 29 Nov 2024 14:18:17 +0100 Subject: [PATCH] [DataGrid] Alter rendering to use table elements (#2664) * Replace grid web components with regular table elements * Adjust styling * Resize rework * - Rename example EventHandlers - Adapt more samples to table rendering - Undo Tabs example changes - Remove grid custom events from script etc * - Redo AutoFit based on table-layout - Clean up CSS, commented code/apply code styles - revert `.col-header` rename (back to `.column-header) to have less breaking changes - Fix some samples * - CSS cleanup * - Remove commented code - Fix resize reset - Make size related methods public - Make GridTemplateColumns work again * Update version * - Fix verified files for DataGrid tests * - Script optimization - Take resize handler out of button * - Rename ShowSortIcon -> IsActiveSortColumn -Update RemoveSortByColumn logic * More optimizations and fixes * - More fine-tunning. - Add `MultiLine` parameter to grid + styling - Make `AutoSize` work again * Fix tests * - Styling updates - Set `RowSize` to small to be equal to v4 default * - Don't use dynamic row size for now. - Process review comments * Fix tests * More style updates * - Re-add separate header buttons - Fix some samples - Position resize handle better - Always put sort/filter indicators behind header text - Add comments to public methods * Fix merge issues * Change icons in grid to size 20 (nice in the middel of Aspire (16) and our default (24) * Fix tests/verified files * Fix virtualized grid row height * Fix column resizing when in rtl mode * - Fix button width when no filter button - Add tabindex to cells - Fix resize handle pos when rtl -WIP: use cursor keys to go through cells * - Update example, - Update rendering for non button column headee - Fix multiline height issue * Fix tests * - Process review comments * - Fix keyboard navigation in DataGrid * Menu work * Fix DataGrid Header menu * Add a start padding for the first header cell * Process compiler hint * - Fix remote data example - Clean Issue tester - Remove commented out code - Fix AutoSize tou use `px` instead of `fr` * Don't use AutoFocus on demo page * Fix scroll on up/down arrow keypress * Fix Select All not aligned correctly * Add Index to ColumnBase * Fix tests --- .../Shared/Components/ApiDocumentation.razor | 6 +- ...crosoft.FluentUI.AspNetCore.Components.xml | 68 +++- .../Shared/Pages/DataGrid/DataGridPage.razor | 2 +- .../DataGrid/Examples/DataGridAutoFit.razor | 2 +- .../Examples/DataGridCustomComparer.razor | 85 ++--- .../Examples/DataGridMultilineText.razor | 6 +- ...ataGridNotVirtualizedLoadingAndEmpty.razor | 4 +- .../Examples/DataGridRemoteData.razor | 9 +- .../Examples/DataGridTableScrollbars.razor | 2 +- .../Examples/DataGridTemplateColumns.razor | 4 +- .../Examples/DataGridTemplateColumns2.razor | 14 +- .../DataGrid/Examples/DataGridTypical.razor | 97 +++-- .../Examples/DataGridTypical.razor.css | 2 +- .../Examples/DataGridVirtualize.razor | 14 +- .../Pages/Tabs/Examples/TabsDynamic.razor | 1 + examples/Demo/Shared/SampleData/DataSource.cs | 2 +- examples/Demo/Shared/wwwroot/css/site.css | 4 +- src/Core.Assets/src/index.ts | 18 - .../DataGrid/Columns/ColumnBase.razor | 158 ++++---- .../DataGrid/Columns/ColumnBase.razor.cs | 25 +- .../DataGrid/Columns/ColumnBase.razor.css | 30 +- .../Columns/ColumnResizeOptions.razor | 1 + .../DataGrid/Columns/SelectColumn.cs | 3 +- .../Components/DataGrid/FluentDataGrid.razor | 144 ++++---- .../DataGrid/FluentDataGrid.razor.cs | 120 +++++-- .../DataGrid/FluentDataGrid.razor.css | 79 ++-- .../DataGrid/FluentDataGrid.razor.js | 338 +++++++++--------- .../DataGrid/FluentDataGridCell.razor | 36 +- .../DataGrid/FluentDataGridCell.razor.cs | 37 +- .../DataGrid/FluentDataGridCell.razor.css | 152 ++++---- .../DataGrid/FluentDataGridRow.razor | 21 +- .../DataGrid/FluentDataGridRow.razor.cs | 42 ++- .../DataGrid/FluentDataGridRow.razor.css | 9 +- src/Core/Components/Icons/CoreIcons.cs | 13 +- .../Components/Menu/FluentMenuProvider.razor | 3 +- src/Core/Enums/DataGridRowSize.cs | 34 ++ src/Core/Events/EventHandlers.cs | 2 - ...yColumnIndex_Ascending.verified.razor.html | 64 ++-- ...ColumnIndex_Descending.verified.razor.html | 64 ++-- ...yColumnTitle_Ascending.verified.razor.html | 64 ++-- ...ColumnTitle_Descending.verified.razor.html | 64 ++-- ...t_Customized_Rendering.verified.razor.html | 54 +-- ..._MultiSelect_Rendering.verified.razor.html | 90 ++--- ...SingleSelect_Rendering.verified.razor.html | 78 ++-- 44 files changed, 1142 insertions(+), 923 deletions(-) create mode 100644 src/Core/Enums/DataGridRowSize.cs diff --git a/examples/Demo/Shared/Components/ApiDocumentation.razor b/examples/Demo/Shared/Components/ApiDocumentation.razor index f27addbad0..11c615da7c 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 aad50f47dc..e1407fac8f 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 a0c6b2f6c8..f0c350d087 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 c3bf9a049f..2ff7d08f3f 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 5202ff2f18..e0df24e86d 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 89dcc9285d..bf68608e07 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 0af35295a0..ffe355d695 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 a5943a326e..8f49828f2c 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 13fe3ab17a..0958218f1d 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 5496035fe5..c1e5d93a78 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 f9b8bcce8e..bdc5171299 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 43fd95f815..41394e2099 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 2aa0928d6d..49724e23df 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 6277b5307e..4e2b26ba8e 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 db13cc8ac5..fa25b58cb4 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 078c6a973d..741c3850e3 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 15feed2986..3655ed8a5b 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 4db995bdaa..b538331646 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 a9e18748bc..469d4f218e 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 def61a9c7d..d0a3a32999 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 632ba1fbd2..cbf496e9c2 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 23d1aed463..120a152342 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 5eaa739ebd..f8cc70cfd0 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 074484ca4a..84f90548f5 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 d795f73b13..8d6942183b 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 d371ef5f67..1122bec7ed 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 2d2f9b4e63..f53e9a6fe0 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 a05de6cc31..c531716d08 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 c81423a886..327a9339c0 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 e866d970d3..b7285afe0e 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 809c817810..ab842f60d2 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 c17580ed54..10fb0db41c 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 e206f8b60d..071d1c6a07 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 1c577f3f39..96c19ec917 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 beb39d4e35..42ade77fb0 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 0000000000..4e1cadce62 --- /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 de4a14a155..a499690ae2 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 54e3b7df6c..668b64c43d 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 7c988e1497..59121cb255 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 07643b861b..668b64c43d 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 7c988e1497..59121cb255 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 7179d1af27..6bd3fb6c69 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 87edd83d2b..45c4837ee9 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 a2b8b67e5d..d59aed3707 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